www in docker support
This commit is contained in:
parent
539a848e95
commit
c227fce036
2145 changed files with 399596 additions and 58 deletions
|
|
@ -0,0 +1,29 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.Demuxer;
|
||||
import org.jcodec.common.DemuxerTrack;
|
||||
import org.jcodec.common.DemuxerTrackMeta;
|
||||
import org.jcodec.common.model.Packet;
|
||||
|
||||
public interface MPEGDemuxer extends Demuxer {
|
||||
List<? extends MPEGDemuxerTrack> getTracks();
|
||||
|
||||
List<? extends MPEGDemuxerTrack> getVideoTracks();
|
||||
|
||||
List<? extends MPEGDemuxerTrack> getAudioTracks();
|
||||
|
||||
public static interface MPEGDemuxerTrack extends DemuxerTrack {
|
||||
Packet nextFrameWithBuffer(ByteBuffer param1ByteBuffer) throws IOException;
|
||||
|
||||
DemuxerTrackMeta getMeta();
|
||||
|
||||
int getSid();
|
||||
|
||||
List<PESPacket> getPending();
|
||||
|
||||
void ignore();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.model.Packet;
|
||||
import org.jcodec.common.model.TapeTimecode;
|
||||
|
||||
public class MPEGPacket extends Packet {
|
||||
private long offset;
|
||||
|
||||
private ByteBuffer seq;
|
||||
|
||||
private int gop;
|
||||
|
||||
private int timecode;
|
||||
|
||||
public MPEGPacket(ByteBuffer data, long pts, int timescale, long duration, long frameNo, Packet.FrameType keyFrame, TapeTimecode tapeTimecode) {
|
||||
super(data, pts, timescale, duration, frameNo, keyFrame, tapeTimecode, 0);
|
||||
}
|
||||
|
||||
public long getOffset() {
|
||||
return this.offset;
|
||||
}
|
||||
|
||||
public ByteBuffer getSeq() {
|
||||
return this.seq;
|
||||
}
|
||||
|
||||
public int getGOP() {
|
||||
return this.gop;
|
||||
}
|
||||
|
||||
public int getTimecode() {
|
||||
return this.timecode;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,497 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
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.aac.AACConts;
|
||||
import org.jcodec.codecs.aac.ADTSParser;
|
||||
import org.jcodec.codecs.h264.io.model.NALUnit;
|
||||
import org.jcodec.codecs.h264.io.model.NALUnitType;
|
||||
import org.jcodec.codecs.mpeg12.MPEGDecoder;
|
||||
import org.jcodec.codecs.mpeg12.MPEGES;
|
||||
import org.jcodec.codecs.mpeg12.SegmentReader;
|
||||
import org.jcodec.common.DemuxerTrackMeta;
|
||||
import org.jcodec.common.IntIntHistogram;
|
||||
import org.jcodec.common.LongArrayList;
|
||||
import org.jcodec.common.TrackType;
|
||||
import org.jcodec.common.UsedViaReflection;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.model.Packet;
|
||||
|
||||
public class MPSDemuxer extends SegmentReader implements MPEGDemuxer {
|
||||
private static final int BUFFER_SIZE = 1048576;
|
||||
|
||||
private Map<Integer, BaseTrack> streams;
|
||||
|
||||
private ReadableByteChannel channel;
|
||||
|
||||
private List<ByteBuffer> bufPool;
|
||||
|
||||
public MPSDemuxer(ReadableByteChannel channel) throws IOException {
|
||||
super(channel, 4096);
|
||||
this.streams = new HashMap<>();
|
||||
this.channel = channel;
|
||||
this.bufPool = new ArrayList<>();
|
||||
findStreams();
|
||||
}
|
||||
|
||||
protected void findStreams() throws IOException {
|
||||
for (int i = 0; i == 0 || (i < 5 * this.streams.size() && this.streams.size() < 2); i++) {
|
||||
PESPacket nextPacket = nextPacket(getBuffer());
|
||||
if (nextPacket == null)
|
||||
break;
|
||||
addToStream(nextPacket);
|
||||
}
|
||||
}
|
||||
|
||||
public ByteBuffer getBuffer() {
|
||||
synchronized (this.bufPool) {
|
||||
if (this.bufPool.size() > 0)
|
||||
return (ByteBuffer)this.bufPool.remove(0);
|
||||
}
|
||||
return ByteBuffer.allocate(1048576);
|
||||
}
|
||||
|
||||
public void putBack(ByteBuffer buffer) {
|
||||
buffer.clear();
|
||||
synchronized (this.bufPool) {
|
||||
this.bufPool.add(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class BaseTrack implements MPEGDemuxer.MPEGDemuxerTrack {
|
||||
protected int streamId;
|
||||
|
||||
protected List<PESPacket> _pending;
|
||||
|
||||
protected MPSDemuxer demuxer;
|
||||
|
||||
public BaseTrack(MPSDemuxer demuxer, int streamId, PESPacket pkt) throws IOException {
|
||||
this._pending = new ArrayList<>();
|
||||
this.demuxer = demuxer;
|
||||
this.streamId = streamId;
|
||||
this._pending.add(pkt);
|
||||
}
|
||||
|
||||
public int getSid() {
|
||||
return this.streamId;
|
||||
}
|
||||
|
||||
public void pending(PESPacket pkt) {
|
||||
if (this._pending != null) {
|
||||
this._pending.add(pkt);
|
||||
} else {
|
||||
this.demuxer.putBack(pkt.data);
|
||||
}
|
||||
}
|
||||
|
||||
public List<PESPacket> getPending() {
|
||||
return this._pending;
|
||||
}
|
||||
|
||||
public void ignore() {
|
||||
if (this._pending == null)
|
||||
return;
|
||||
for (PESPacket pesPacket : this._pending)
|
||||
this.demuxer.putBack(pesPacket.data);
|
||||
this._pending = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MPEGTrack extends BaseTrack implements ReadableByteChannel {
|
||||
private MPEGES es;
|
||||
|
||||
private LongArrayList ptsSeen;
|
||||
|
||||
private long lastPts;
|
||||
|
||||
private int lastSeq;
|
||||
|
||||
private int lastSeqSeen;
|
||||
|
||||
private int seqWrap;
|
||||
|
||||
private IntIntHistogram durationHistogram;
|
||||
|
||||
public MPEGTrack(MPSDemuxer demuxer, int streamId, PESPacket pkt) throws IOException {
|
||||
super(demuxer, streamId, pkt);
|
||||
this.es = new MPEGES(this, 4096);
|
||||
this.ptsSeen = new LongArrayList(32);
|
||||
this.lastSeq = Integer.MIN_VALUE;
|
||||
this.lastSeqSeen = 2147482647;
|
||||
this.seqWrap = 2147482647;
|
||||
this.durationHistogram = new IntIntHistogram();
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public MPEGES getES() {
|
||||
return this.es;
|
||||
}
|
||||
|
||||
public void close() throws IOException {}
|
||||
|
||||
public int read(ByteBuffer arg0) throws IOException {
|
||||
PESPacket pes = (this._pending.size() > 0) ? (PESPacket)this._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()) {
|
||||
this._pending.add(0, pes);
|
||||
} else {
|
||||
this.demuxer.putBack(pes.data);
|
||||
}
|
||||
return toRead;
|
||||
}
|
||||
|
||||
private PESPacket getPacket() throws IOException {
|
||||
if (this._pending.size() > 0)
|
||||
return (PESPacket)this._pending.remove(0);
|
||||
PESPacket pkt;
|
||||
while ((pkt = this.demuxer.nextPacket(this.demuxer.getBuffer())) != null) {
|
||||
if (pkt.streamId == this.streamId) {
|
||||
if (pkt.pts != -1L)
|
||||
this.ptsSeen.add(pkt.pts);
|
||||
return pkt;
|
||||
}
|
||||
this.demuxer.addToStream(pkt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Packet nextFrameWithBuffer(ByteBuffer buf) throws IOException {
|
||||
return this.es.frame(buf);
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
MPEGPacket pkt = this.es.getFrame();
|
||||
if (pkt == null)
|
||||
return null;
|
||||
int seq = MPEGDecoder.getSequenceNumber(pkt.getData());
|
||||
if (seq == 0)
|
||||
this.seqWrap = this.lastSeqSeen + 1;
|
||||
this.lastSeqSeen = seq;
|
||||
if (this.ptsSeen.size() <= 0) {
|
||||
pkt.setPts((long)(Math.min(seq - this.lastSeq, seq - this.lastSeq + this.seqWrap) * this.durationHistogram.max()) + this.lastPts);
|
||||
} else {
|
||||
pkt.setPts(this.ptsSeen.shift());
|
||||
if (this.lastSeq >= 0 && seq > this.lastSeq)
|
||||
this.durationHistogram.increment((int)(pkt.getPts() - this.lastPts) /
|
||||
Math.min(seq - this.lastSeq, seq - this.lastSeq + this.seqWrap));
|
||||
this.lastPts = pkt.getPts();
|
||||
this.lastSeq = seq;
|
||||
}
|
||||
pkt.setDuration((long)this.durationHistogram.max());
|
||||
System.out.println(seq);
|
||||
return pkt;
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlainTrack extends BaseTrack {
|
||||
private int frameNo;
|
||||
|
||||
private Packet lastFrame;
|
||||
|
||||
private long lastKnownDuration = 3003L;
|
||||
|
||||
public PlainTrack(MPSDemuxer demuxer, int streamId, PESPacket pkt) throws IOException {
|
||||
super(demuxer, streamId, pkt);
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void close() throws IOException {}
|
||||
|
||||
public Packet nextFrameWithBuffer(ByteBuffer buf) throws IOException {
|
||||
PESPacket pkt;
|
||||
if (this._pending.size() > 0) {
|
||||
pkt = (PESPacket)this._pending.remove(0);
|
||||
} else {
|
||||
while ((pkt = this.demuxer.nextPacket(this.demuxer.getBuffer())) != null && pkt.streamId != this.streamId)
|
||||
this.demuxer.addToStream(pkt);
|
||||
}
|
||||
return (pkt == null) ? null : Packet.createPacket(pkt.data, pkt.pts, 90000, 0L, (long)this.frameNo++, Packet.FrameType.UNKNOWN, null);
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
if (this.lastFrame == null)
|
||||
this.lastFrame = nextFrameWithBuffer(null);
|
||||
if (this.lastFrame == null)
|
||||
return null;
|
||||
Packet toReturn = this.lastFrame;
|
||||
this.lastFrame = nextFrameWithBuffer(null);
|
||||
if (this.lastFrame != null)
|
||||
this.lastKnownDuration = this.lastFrame.getPts() - toReturn.getPts();
|
||||
toReturn.setDuration(this.lastKnownDuration);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
TrackType t = MPSUtils.videoStream(this.streamId) ? TrackType.VIDEO : (MPSUtils.audioStream(this.streamId) ? TrackType.AUDIO : TrackType.OTHER);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AACTrack extends PlainTrack {
|
||||
private List<Packet> audioStash;
|
||||
|
||||
public AACTrack(MPSDemuxer demuxer, int streamId, PESPacket pkt) throws IOException {
|
||||
super(demuxer, streamId, pkt);
|
||||
this.audioStash = new ArrayList<>();
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
if (this.audioStash.size() == 0) {
|
||||
Packet nextFrame = nextFrameWithBuffer(null);
|
||||
if (nextFrame != null) {
|
||||
ByteBuffer data = nextFrame.getData();
|
||||
ADTSParser.Header adts = ADTSParser.read(data.duplicate());
|
||||
long nextPts = nextFrame.getPts();
|
||||
while (data.hasRemaining()) {
|
||||
ByteBuffer data2 = NIOUtils.read(data, adts.getSize());
|
||||
Packet pkt = Packet.createPacketWithData(nextFrame, data2);
|
||||
pkt.setDuration(
|
||||
(long)(pkt.getTimescale() * 1024 / AACConts.AAC_SAMPLE_RATES[adts.getSamplingIndex()]));
|
||||
pkt.setPts(nextPts);
|
||||
nextPts += pkt.getDuration();
|
||||
this.audioStash.add(pkt);
|
||||
if (data.hasRemaining())
|
||||
adts = ADTSParser.read(data.duplicate());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.audioStash.size() == 0)
|
||||
return null;
|
||||
return (Packet)this.audioStash.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
for (BaseTrack track : this.streams.values())
|
||||
track._pending.clear();
|
||||
}
|
||||
|
||||
private void addToStream(PESPacket pkt) throws IOException {
|
||||
BaseTrack pes = this.streams.get(Integer.valueOf(pkt.streamId));
|
||||
if (pes == null) {
|
||||
if (isMPEG(pkt.data)) {
|
||||
pes = new MPEGTrack(this, pkt.streamId, pkt);
|
||||
} else if (isAAC(pkt.data)) {
|
||||
pes = new AACTrack(this, pkt.streamId, pkt);
|
||||
} else {
|
||||
pes = new PlainTrack(this, pkt.streamId, pkt);
|
||||
}
|
||||
this.streams.put(Integer.valueOf(pkt.streamId), pes);
|
||||
} else {
|
||||
pes.pending(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
public PESPacket nextPacket(ByteBuffer out) throws IOException {
|
||||
ByteBuffer dup = out.duplicate();
|
||||
while (!MPSUtils.psMarker(this.curMarker)) {
|
||||
if (!skipToMarker())
|
||||
return null;
|
||||
}
|
||||
ByteBuffer fork = dup.duplicate();
|
||||
readToNextMarker(dup);
|
||||
PESPacket pkt = MPSUtils.readPESHeader(fork, curPos());
|
||||
if (pkt.length == 0) {
|
||||
while (!MPSUtils.psMarker(this.curMarker) && readToNextMarker(dup));
|
||||
} else {
|
||||
read(dup, pkt.length - dup.position() + 6);
|
||||
}
|
||||
fork.limit(dup.position());
|
||||
pkt.data = fork;
|
||||
return pkt;
|
||||
}
|
||||
|
||||
public List<MPEGDemuxer.MPEGDemuxerTrack> getTracks() {
|
||||
return new ArrayList<>(this.streams.values());
|
||||
}
|
||||
|
||||
public List<MPEGDemuxer.MPEGDemuxerTrack> getVideoTracks() {
|
||||
List<MPEGDemuxer.MPEGDemuxerTrack> result = new ArrayList<>();
|
||||
for (BaseTrack p : this.streams.values()) {
|
||||
if (MPSUtils.videoStream(p.streamId))
|
||||
result.add(p);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<MPEGDemuxer.MPEGDemuxerTrack> getAudioTracks() {
|
||||
List<MPEGDemuxer.MPEGDemuxerTrack> result = new ArrayList<>();
|
||||
for (BaseTrack p : this.streams.values()) {
|
||||
if (MPSUtils.audioStream(p.streamId))
|
||||
result.add(p);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isAAC(ByteBuffer _data) {
|
||||
ADTSParser.Header read = ADTSParser.read(_data.duplicate());
|
||||
return (read != null);
|
||||
}
|
||||
|
||||
private boolean isMPEG(ByteBuffer _data) {
|
||||
ByteBuffer b = _data.duplicate();
|
||||
int marker = -1;
|
||||
int score = 0;
|
||||
boolean hasHeader = false, slicesStarted = false;
|
||||
while (b.hasRemaining()) {
|
||||
int code = b.get() & 0xFF;
|
||||
marker = marker << 8 | code;
|
||||
if (marker < 256 || marker > 440)
|
||||
continue;
|
||||
if (marker >= 432 && marker <= 440) {
|
||||
if ((hasHeader && marker != 437 && marker != 434) || slicesStarted)
|
||||
break;
|
||||
score += 5;
|
||||
continue;
|
||||
}
|
||||
if (marker == 256) {
|
||||
if (slicesStarted)
|
||||
break;
|
||||
hasHeader = true;
|
||||
continue;
|
||||
}
|
||||
if (marker > 256 && marker < 432) {
|
||||
if (!hasHeader)
|
||||
break;
|
||||
if (!slicesStarted) {
|
||||
score += 50;
|
||||
slicesStarted = true;
|
||||
}
|
||||
score++;
|
||||
}
|
||||
}
|
||||
return (score > 50);
|
||||
}
|
||||
|
||||
@UsedViaReflection
|
||||
public static int probe(ByteBuffer b_) {
|
||||
ByteBuffer b = b_.duplicate();
|
||||
int marker = -1;
|
||||
int sliceSize = 0;
|
||||
boolean videoPes = false;
|
||||
int state = 0;
|
||||
int errors = 0;
|
||||
boolean inNALUnit = false;
|
||||
List<NALUnit> nuSeq = new ArrayList<>();
|
||||
while (b.hasRemaining()) {
|
||||
int code = b.get() & 0xFF;
|
||||
if (state >= 3)
|
||||
sliceSize++;
|
||||
marker = marker << 8 | code;
|
||||
if (inNALUnit) {
|
||||
NALUnit nu = NALUnit.read(NIOUtils.asByteBufferInt(new int[] { code }));
|
||||
if (nu.type != null)
|
||||
nuSeq.add(nu);
|
||||
inNALUnit = false;
|
||||
}
|
||||
if (videoPes && marker == 1) {
|
||||
inNALUnit = true;
|
||||
continue;
|
||||
}
|
||||
if (marker < 256 || marker > 511)
|
||||
continue;
|
||||
if (marker >= 442) {
|
||||
videoPes = MPSUtils.videoMarker(marker);
|
||||
continue;
|
||||
}
|
||||
if (!videoPes)
|
||||
continue;
|
||||
boolean stop = false;
|
||||
switch (state) {
|
||||
case 0:
|
||||
if (marker >= 432 && marker <= 440) {
|
||||
state = 1;
|
||||
} else if (marker == 256) {
|
||||
state = 2;
|
||||
} else {
|
||||
state = 0;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if (marker == 256) {
|
||||
state = 2;
|
||||
} else if (marker >= 432 && marker <= 440) {
|
||||
state = 1;
|
||||
} else {
|
||||
errors++;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (marker == 257) {
|
||||
state = 3;
|
||||
} else if (marker == 437 || marker == 434) {
|
||||
state = 2;
|
||||
} else {
|
||||
errors++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (state > 3 && sliceSize < 1)
|
||||
errors++;
|
||||
sliceSize = 0;
|
||||
if (state - 1 == marker - 256) {
|
||||
state = marker - 256 + 2;
|
||||
break;
|
||||
}
|
||||
if (marker == 256 || marker >= 432)
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
if (stop)
|
||||
break;
|
||||
}
|
||||
return Math.max(rateSeq(nuSeq), (state >= 3) ? (100 / (1 + errors)) : 0);
|
||||
}
|
||||
|
||||
private static int rateSeq(List<NALUnit> nuSeq) {
|
||||
int score = 0;
|
||||
boolean hasSps = false, hasPps = false, hasSlice = false;
|
||||
for (NALUnit nalUnit : nuSeq) {
|
||||
if (NALUnitType.SPS == nalUnit.type) {
|
||||
if (hasSps && !hasSlice) {
|
||||
score -= 30;
|
||||
} else {
|
||||
score += 30;
|
||||
}
|
||||
hasSps = true;
|
||||
continue;
|
||||
}
|
||||
if (NALUnitType.PPS == nalUnit.type) {
|
||||
if (hasPps && !hasSlice)
|
||||
score -= 30;
|
||||
if (hasSps)
|
||||
score += 20;
|
||||
hasPps = true;
|
||||
continue;
|
||||
}
|
||||
if (NALUnitType.IDR_SLICE == nalUnit.type || NALUnitType.NON_IDR_SLICE == nalUnit.type) {
|
||||
if (!hasSlice)
|
||||
score += 20;
|
||||
hasSlice = true;
|
||||
}
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
this.channel.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Arrays;
|
||||
import org.jcodec.codecs.mpeg12.MPEGUtil;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.CopyrightExtension;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.GOPHeader;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.PictureCodingExtension;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.PictureDisplayExtension;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.PictureHeader;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.PictureSpatialScalableExtension;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.PictureTemporalScalableExtension;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.QuantMatrixExtension;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.SequenceDisplayExtension;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.SequenceExtension;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.SequenceHeader;
|
||||
import org.jcodec.codecs.mpeg12.bitstream.SequenceScalableExtension;
|
||||
import org.jcodec.common.io.BitReader;
|
||||
import org.jcodec.common.io.FileChannelWrapper;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.tools.MainUtils;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class MPSDump {
|
||||
private static final MainUtils.Flag DUMP_FROM = MainUtils.Flag.flag("dump-from", null, "Stop reading at timestamp");
|
||||
|
||||
private static final MainUtils.Flag STOP_AT = MainUtils.Flag.flag("stop-at", null, "Start dumping from timestamp");
|
||||
|
||||
private static final MainUtils.Flag[] ALL_FLAGS = new MainUtils.Flag[] { DUMP_FROM, STOP_AT };
|
||||
|
||||
protected ReadableByteChannel ch;
|
||||
|
||||
public MPSDump(ReadableByteChannel ch) {
|
||||
this.ch = ch;
|
||||
}
|
||||
|
||||
public static void main1(String[] args) throws IOException {
|
||||
FileChannelWrapper ch = null;
|
||||
try {
|
||||
MainUtils.Cmd cmd = MainUtils.parseArguments(args, ALL_FLAGS);
|
||||
if (cmd.args.length < 1) {
|
||||
MainUtils.printHelp(ALL_FLAGS, Arrays.asList("file name"));
|
||||
return;
|
||||
}
|
||||
ch = NIOUtils.readableChannel(new File(cmd.args[0]));
|
||||
Long dumpAfterPts = cmd.getLongFlag(DUMP_FROM);
|
||||
Long stopPts = cmd.getLongFlag(STOP_AT);
|
||||
new MPSDump(ch).dump(dumpAfterPts, stopPts);
|
||||
} finally {
|
||||
NIOUtils.closeQuietly(ch);
|
||||
}
|
||||
}
|
||||
|
||||
public void dump(Long dumpAfterPts, Long stopPts) throws IOException {
|
||||
MPEGVideoAnalyzer analyzer = null;
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1048576);
|
||||
PESPacket pkt = null;
|
||||
int hdrSize = 0;
|
||||
long position = 0L;
|
||||
while (true) {
|
||||
position -= (long)buffer.position();
|
||||
if (fillBuffer(buffer) == -1)
|
||||
break;
|
||||
buffer.flip();
|
||||
if (buffer.remaining() < 4)
|
||||
break;
|
||||
position += (long)buffer.remaining();
|
||||
while (true) {
|
||||
ByteBuffer payload = null;
|
||||
if (pkt != null && pkt.length > 0) {
|
||||
int pesLen = pkt.length - hdrSize + 6;
|
||||
if (pesLen <= buffer.remaining())
|
||||
payload = NIOUtils.read(buffer, pesLen);
|
||||
} else {
|
||||
payload = getPesPayload(buffer);
|
||||
}
|
||||
if (payload == null)
|
||||
break;
|
||||
if (pkt != null)
|
||||
logPes(pkt, hdrSize, payload);
|
||||
if (analyzer != null && pkt != null && pkt.streamId >= 224 && pkt.streamId <= 239)
|
||||
analyzer.analyzeMpegVideoPacket(payload);
|
||||
if (buffer.remaining() < 32) {
|
||||
pkt = null;
|
||||
break;
|
||||
}
|
||||
skipToNextPES(buffer);
|
||||
if (buffer.remaining() < 32) {
|
||||
pkt = null;
|
||||
break;
|
||||
}
|
||||
hdrSize = buffer.position();
|
||||
pkt = MPSUtils.readPESHeader(buffer, position - (long)buffer.remaining());
|
||||
hdrSize = buffer.position() - hdrSize;
|
||||
if (dumpAfterPts != null && pkt.pts >= dumpAfterPts)
|
||||
analyzer = new MPEGVideoAnalyzer();
|
||||
if (stopPts != null && pkt.pts >= stopPts)
|
||||
return;
|
||||
}
|
||||
buffer = transferRemainder(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
protected int fillBuffer(ByteBuffer buffer) throws IOException {
|
||||
return this.ch.read(buffer);
|
||||
}
|
||||
|
||||
protected void logPes(PESPacket pkt, int hdrSize, ByteBuffer payload) {
|
||||
System.out.println("" + pkt.streamId + "(" + pkt.streamId + ") [" + ((pkt.streamId >= 224) ? "video" : "audio") + ", " + pkt.pos + "], pts: " +
|
||||
payload.remaining() + hdrSize + ", dts: " + pkt.pts);
|
||||
}
|
||||
|
||||
private ByteBuffer transferRemainder(ByteBuffer buffer) {
|
||||
ByteBuffer dup = buffer.duplicate();
|
||||
dup.clear();
|
||||
while (buffer.hasRemaining())
|
||||
dup.put(buffer.get());
|
||||
return dup;
|
||||
}
|
||||
|
||||
private static void skipToNextPES(ByteBuffer buffer) {
|
||||
while (buffer.hasRemaining()) {
|
||||
int marker = buffer.duplicate().getInt();
|
||||
if (marker >= 445 && marker <= 511 && marker != 446)
|
||||
break;
|
||||
buffer.getInt();
|
||||
MPEGUtil.gotoNextMarker(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer getPesPayload(ByteBuffer buffer) {
|
||||
ByteBuffer copy = buffer.duplicate();
|
||||
ByteBuffer result = buffer.duplicate();
|
||||
while (copy.hasRemaining()) {
|
||||
int marker = copy.duplicate().getInt();
|
||||
if (marker >= 441) {
|
||||
result.limit(copy.position());
|
||||
buffer.position(copy.position());
|
||||
return result;
|
||||
}
|
||||
copy.getInt();
|
||||
MPEGUtil.gotoNextMarker(copy);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static class MPEGVideoAnalyzer {
|
||||
private int nextStartCode = -1;
|
||||
|
||||
private ByteBuffer bselPayload;
|
||||
|
||||
private int bselStartCode;
|
||||
|
||||
private int bselOffset;
|
||||
|
||||
private int bselBufInd;
|
||||
|
||||
private int prevBufSize;
|
||||
|
||||
private int curBufInd;
|
||||
|
||||
private PictureHeader picHeader;
|
||||
|
||||
private SequenceHeader sequenceHeader;
|
||||
|
||||
private PictureCodingExtension pictureCodingExtension;
|
||||
|
||||
private SequenceExtension sequenceExtension;
|
||||
|
||||
public MPEGVideoAnalyzer() {
|
||||
this.bselPayload = ByteBuffer.allocate(1048576);
|
||||
}
|
||||
|
||||
private void analyzeMpegVideoPacket(ByteBuffer buffer) {
|
||||
int pos = buffer.position();
|
||||
int bufSize = buffer.remaining();
|
||||
while (buffer.hasRemaining()) {
|
||||
this.bselPayload.put((byte)(this.nextStartCode >> 24));
|
||||
this.nextStartCode = this.nextStartCode << 8 | buffer.get() & 0xFF;
|
||||
if (this.nextStartCode >= 256 && this.nextStartCode <= 440) {
|
||||
this.bselPayload.flip();
|
||||
this.bselPayload.getInt();
|
||||
if (this.bselStartCode != 0) {
|
||||
if (this.bselBufInd != this.curBufInd)
|
||||
this.bselOffset -= this.prevBufSize;
|
||||
dumpBSEl(this.bselStartCode, this.bselOffset, this.bselPayload);
|
||||
}
|
||||
this.bselPayload.clear();
|
||||
this.bselStartCode = this.nextStartCode;
|
||||
this.bselOffset = buffer.position() - 4 - pos;
|
||||
this.bselBufInd = this.curBufInd;
|
||||
}
|
||||
}
|
||||
this.curBufInd++;
|
||||
this.prevBufSize = bufSize;
|
||||
}
|
||||
|
||||
private void dumpBSEl(int mark, int offset, ByteBuffer b) {
|
||||
System.out.print(String.format("marker: 0x%02x [@%d] ( ", mark, offset));
|
||||
if (mark == 256) {
|
||||
dumpPictureHeader(b);
|
||||
} else if (mark <= 431) {
|
||||
System.out.print(MainUtils.colorBright(String.format("slice @0x%02x", mark - 257), MainUtils.ANSIColor.BLACK, true));
|
||||
} else if (mark == 435) {
|
||||
dumpSequenceHeader(b);
|
||||
} else if (mark == 437) {
|
||||
dumpExtension(b);
|
||||
} else if (mark == 440) {
|
||||
dumpGroupHeader(b);
|
||||
} else {
|
||||
System.out.print("--");
|
||||
}
|
||||
System.out.println(" )");
|
||||
}
|
||||
|
||||
private void dumpExtension(ByteBuffer b) {
|
||||
BitReader _in = BitReader.createBitReader(b);
|
||||
int extType = _in.readNBit(4);
|
||||
if (this.picHeader == null) {
|
||||
if (this.sequenceHeader != null) {
|
||||
switch (extType) {
|
||||
case 1:
|
||||
this.sequenceExtension = SequenceExtension.read(_in);
|
||||
dumpSequenceExtension(this.sequenceExtension);
|
||||
break;
|
||||
case 5:
|
||||
dumpSequenceScalableExtension(SequenceScalableExtension.read(_in));
|
||||
break;
|
||||
case 2:
|
||||
dumpSequenceDisplayExtension(SequenceDisplayExtension.read(_in));
|
||||
break;
|
||||
default:
|
||||
System.out.print(MainUtils.colorBright("extension " + extType, MainUtils.ANSIColor.GREEN, true));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
System.out.print(MainUtils.colorBright("dangling extension " + extType, MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
} else {
|
||||
switch (extType) {
|
||||
case 3:
|
||||
dumpQuantMatrixExtension(QuantMatrixExtension.read(_in));
|
||||
break;
|
||||
case 4:
|
||||
dumpCopyrightExtension(CopyrightExtension.read(_in));
|
||||
break;
|
||||
case 7:
|
||||
if (this.sequenceHeader != null && this.pictureCodingExtension != null)
|
||||
dumpPictureDisplayExtension(PictureDisplayExtension.read(_in, this.sequenceExtension, this.pictureCodingExtension));
|
||||
break;
|
||||
case 8:
|
||||
this.pictureCodingExtension = PictureCodingExtension.read(_in);
|
||||
dumpPictureCodingExtension(this.pictureCodingExtension);
|
||||
break;
|
||||
case 9:
|
||||
dumpPictureSpatialScalableExtension(PictureSpatialScalableExtension.read(_in));
|
||||
break;
|
||||
case 16:
|
||||
dumpPictureTemporalScalableExtension(PictureTemporalScalableExtension.read(_in));
|
||||
break;
|
||||
default:
|
||||
System.out.print(MainUtils.colorBright("extension " + extType, MainUtils.ANSIColor.GREEN, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpSequenceDisplayExtension(SequenceDisplayExtension read) {
|
||||
System.out.print(MainUtils.colorBright("sequence display extension " + dumpBin(read), MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
|
||||
private void dumpSequenceScalableExtension(SequenceScalableExtension read) {
|
||||
System.out.print(MainUtils.colorBright("sequence scalable extension " + dumpBin(read), MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
|
||||
private void dumpSequenceExtension(SequenceExtension read) {
|
||||
System.out.print(MainUtils.colorBright("sequence extension " + dumpBin(read), MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
|
||||
private void dumpPictureTemporalScalableExtension(PictureTemporalScalableExtension read) {
|
||||
System.out.print(MainUtils.colorBright("picture temporal scalable extension " + dumpBin(read), MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
|
||||
private void dumpPictureSpatialScalableExtension(PictureSpatialScalableExtension read) {
|
||||
System.out.print(MainUtils.colorBright("picture spatial scalable extension " + dumpBin(read), MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
|
||||
private void dumpPictureCodingExtension(PictureCodingExtension read) {
|
||||
System.out.print(MainUtils.colorBright("picture coding extension " + dumpBin(read), MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
|
||||
private void dumpPictureDisplayExtension(PictureDisplayExtension read) {
|
||||
System.out.print(MainUtils.colorBright("picture display extension " + dumpBin(read), MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
|
||||
private void dumpCopyrightExtension(CopyrightExtension read) {
|
||||
System.out.print(MainUtils.colorBright("copyright extension " + dumpBin(read), MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
|
||||
private void dumpQuantMatrixExtension(QuantMatrixExtension read) {
|
||||
System.out.print(
|
||||
MainUtils.colorBright("quant matrix extension " + dumpBin(read), MainUtils.ANSIColor.GREEN, true));
|
||||
}
|
||||
|
||||
private String dumpBin(Object read) {
|
||||
StringBuilder bldr = new StringBuilder();
|
||||
bldr.append("<");
|
||||
Field[] fields = Platform.getFields(read.getClass());
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
if (Modifier.isPublic(fields[i].getModifiers()) && !Modifier.isStatic(fields[i].getModifiers())) {
|
||||
bldr.append(convertName(fields[i].getName()) + ": ");
|
||||
if (fields[i].getType().isPrimitive()) {
|
||||
try {
|
||||
bldr.append(fields[i].get(read));
|
||||
} catch (Exception e) {}
|
||||
} else {
|
||||
try {
|
||||
Object val = fields[i].get(read);
|
||||
if (val != null) {
|
||||
bldr.append(dumpBin(val));
|
||||
} else {
|
||||
bldr.append("N/A");
|
||||
}
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
if (i < fields.length - 1)
|
||||
bldr.append(",");
|
||||
}
|
||||
}
|
||||
bldr.append(">");
|
||||
return bldr.toString();
|
||||
}
|
||||
|
||||
private String convertName(String name) {
|
||||
return name.replaceAll("([A-Z])", " $1").replaceFirst("^ ", "").toLowerCase();
|
||||
}
|
||||
|
||||
private void dumpGroupHeader(ByteBuffer b) {
|
||||
GOPHeader gopHeader = GOPHeader.read(b);
|
||||
System.out.print(MainUtils.colorBright("group header <closed:" + gopHeader.isClosedGop() + ",broken link:" +
|
||||
gopHeader.isBrokenLink() + (
|
||||
(gopHeader.getTimeCode() != null) ? (",timecode:" + gopHeader.getTimeCode().toString()) : "") + ">", MainUtils.ANSIColor.MAGENTA, true));
|
||||
}
|
||||
|
||||
private void dumpSequenceHeader(ByteBuffer b) {
|
||||
this.picHeader = null;
|
||||
this.pictureCodingExtension = null;
|
||||
this.sequenceExtension = null;
|
||||
this.sequenceHeader = SequenceHeader.read(b);
|
||||
System.out.print(MainUtils.colorBright("sequence header", MainUtils.ANSIColor.BLUE, true));
|
||||
}
|
||||
|
||||
private void dumpPictureHeader(ByteBuffer b) {
|
||||
this.picHeader = PictureHeader.read(b);
|
||||
this.pictureCodingExtension = null;
|
||||
System.out.print(MainUtils.colorBright("picture header <type:" + (
|
||||
(this.picHeader.picture_coding_type == 1) ? "I" : ((this.picHeader.picture_coding_type == 2) ? "P" : "B")) + ", temp_ref:" + this.picHeader.temporal_reference + ">", MainUtils.ANSIColor.BROWN, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,510 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.IntArrayList;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.model.Rational;
|
||||
|
||||
public class MPSUtils {
|
||||
public static final int VIDEO_MIN = 480;
|
||||
|
||||
public static final int VIDEO_MAX = 495;
|
||||
|
||||
public static final int AUDIO_MIN = 448;
|
||||
|
||||
public static final int AUDIO_MAX = 479;
|
||||
|
||||
public static final int PACK = 442;
|
||||
|
||||
public static final int SYSTEM = 443;
|
||||
|
||||
public static final int PSM = 444;
|
||||
|
||||
public static final int PRIVATE_1 = 445;
|
||||
|
||||
public static final int PRIVATE_2 = 447;
|
||||
|
||||
public static final boolean mediaStream(int streamId) {
|
||||
return ((streamId >= $(448) && streamId <= $(495)) || streamId == $(445) || streamId ==
|
||||
$(447));
|
||||
}
|
||||
|
||||
public static final boolean mediaMarker(int marker) {
|
||||
return ((marker >= 448 && marker <= 495) || marker == 445 || marker == 447);
|
||||
}
|
||||
|
||||
public static final boolean psMarker(int marker) {
|
||||
return (marker >= 445 && marker <= 495);
|
||||
}
|
||||
|
||||
public static boolean videoMarker(int marker) {
|
||||
return (marker >= 480 && marker <= 495);
|
||||
}
|
||||
|
||||
public static final boolean videoStream(int streamId) {
|
||||
return (streamId >= $(480) && streamId <= $(495));
|
||||
}
|
||||
|
||||
public static boolean audioStream(int streamId) {
|
||||
return ((streamId >= $(448) && streamId <= $(479)) || streamId == $(445) || streamId ==
|
||||
$(447));
|
||||
}
|
||||
|
||||
static int $(int marker) {
|
||||
return marker & 0xFF;
|
||||
}
|
||||
|
||||
public static abstract class PESReader {
|
||||
private int marker = -1;
|
||||
|
||||
private int lenFieldLeft;
|
||||
|
||||
private int pesLen;
|
||||
|
||||
private long pesFileStart = -1L;
|
||||
|
||||
private int stream;
|
||||
|
||||
private boolean _pes;
|
||||
|
||||
private int pesLeft;
|
||||
|
||||
private ByteBuffer pesBuffer;
|
||||
|
||||
public PESReader() {
|
||||
this.pesBuffer = ByteBuffer.allocate(2097152);
|
||||
}
|
||||
|
||||
protected abstract void pes(ByteBuffer param1ByteBuffer, long param1Long, int param1Int1, int param1Int2);
|
||||
|
||||
public void analyseBuffer(ByteBuffer buf, long pos) {
|
||||
int init = buf.position();
|
||||
while (buf.hasRemaining()) {
|
||||
if (this.pesLeft > 0) {
|
||||
int toRead = Math.min(buf.remaining(), this.pesLeft);
|
||||
this.pesBuffer.put(NIOUtils.read(buf, toRead));
|
||||
this.pesLeft -= toRead;
|
||||
if (this.pesLeft == 0) {
|
||||
long filePos = pos + (long)buf.position() - (long)init;
|
||||
pes1(this.pesBuffer, this.pesFileStart, (int)(filePos - this.pesFileStart), this.stream);
|
||||
this.pesFileStart = -1L;
|
||||
this._pes = false;
|
||||
this.stream = -1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
int bt = buf.get() & 0xFF;
|
||||
if (this._pes)
|
||||
this.pesBuffer.put((byte)(this.marker >>> 24));
|
||||
this.marker = this.marker << 8 | bt;
|
||||
if (this.marker >= 443 && this.marker <= 495) {
|
||||
long filePos = pos + (long)buf.position() - (long)init - 4L;
|
||||
if (this._pes)
|
||||
pes1(this.pesBuffer, this.pesFileStart, (int)(filePos - this.pesFileStart), this.stream);
|
||||
this.pesFileStart = filePos;
|
||||
this._pes = true;
|
||||
this.stream = this.marker & 0xFF;
|
||||
this.lenFieldLeft = 2;
|
||||
this.pesLen = 0;
|
||||
continue;
|
||||
}
|
||||
if (this.marker >= 441 && this.marker <= 511) {
|
||||
if (this._pes) {
|
||||
long filePos = pos + (long)buf.position() - (long)init - 4L;
|
||||
pes1(this.pesBuffer, this.pesFileStart, (int)(filePos - this.pesFileStart), this.stream);
|
||||
}
|
||||
this.pesFileStart = -1L;
|
||||
this._pes = false;
|
||||
this.stream = -1;
|
||||
continue;
|
||||
}
|
||||
if (this.lenFieldLeft > 0) {
|
||||
this.pesLen = this.pesLen << 8 | bt;
|
||||
this.lenFieldLeft--;
|
||||
if (this.lenFieldLeft == 0) {
|
||||
this.pesLeft = this.pesLen;
|
||||
if (this.pesLen != 0) {
|
||||
flushMarker();
|
||||
this.marker = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void flushMarker() {
|
||||
this.pesBuffer.put((byte)(this.marker >>> 24));
|
||||
this.pesBuffer.put((byte)(this.marker >>> 16 & 0xFF));
|
||||
this.pesBuffer.put((byte)(this.marker >>> 8 & 0xFF));
|
||||
this.pesBuffer.put((byte)(this.marker & 0xFF));
|
||||
}
|
||||
|
||||
private void pes1(ByteBuffer pesBuffer, long start, int pesLen, int stream) {
|
||||
pesBuffer.flip();
|
||||
pes(pesBuffer, start, pesLen, stream);
|
||||
pesBuffer.clear();
|
||||
}
|
||||
|
||||
public void finishRead() {
|
||||
if (this.pesLeft <= 4) {
|
||||
flushMarker();
|
||||
pes1(this.pesBuffer, this.pesFileStart, this.pesBuffer.position(), this.stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static PESPacket readPESHeader(ByteBuffer iss, long pos) {
|
||||
int streamId = iss.getInt() & 0xFF;
|
||||
int len = iss.getShort() & 0xFFFF;
|
||||
if (streamId != 191) {
|
||||
int b0 = iss.get() & 0xFF;
|
||||
if ((b0 & 0xC0) == 128)
|
||||
return mpeg2Pes(b0, len, streamId, iss, pos);
|
||||
return mpeg1Pes(b0, len, streamId, iss, pos);
|
||||
}
|
||||
return new PESPacket(null, -1L, streamId, len, pos, -1L);
|
||||
}
|
||||
|
||||
public static PESPacket mpeg1Pes(int b0, int len, int streamId, ByteBuffer is, long pos) {
|
||||
int c = b0;
|
||||
while (c == 255)
|
||||
c = is.get() & 0xFF;
|
||||
if ((c & 0xC0) == 64) {
|
||||
is.get();
|
||||
c = is.get() & 0xFF;
|
||||
}
|
||||
long pts = -1L, dts = -1L;
|
||||
if ((c & 0xF0) == 32) {
|
||||
pts = _readTs(is, c);
|
||||
} else if ((c & 0xF0) == 48) {
|
||||
pts = _readTs(is, c);
|
||||
dts = readTs(is);
|
||||
} else if (c != 15) {
|
||||
throw new RuntimeException("Invalid data");
|
||||
}
|
||||
return new PESPacket(null, pts, streamId, len, pos, dts);
|
||||
}
|
||||
|
||||
public static long _readTs(ByteBuffer is, int c) {
|
||||
return ((long)c & 0xEL) << 29L | (long)((is.get() & 0xFF) << 22) | (long)((is.get() & 0xFF) >> 1 << 15) |
|
||||
(long)((is.get() & 0xFF) << 7) | (long)((is.get() & 0xFF) >> 1);
|
||||
}
|
||||
|
||||
public static PESPacket mpeg2Pes(int b0, int len, int streamId, ByteBuffer is, long pos) {
|
||||
int flags1 = b0;
|
||||
int flags2 = is.get() & 0xFF;
|
||||
int header_len = is.get() & 0xFF;
|
||||
long pts = -1L, dts = -1L;
|
||||
if ((flags2 & 0xC0) == 128) {
|
||||
pts = readTs(is);
|
||||
NIOUtils.skip(is, header_len - 5);
|
||||
} else if ((flags2 & 0xC0) == 192) {
|
||||
pts = readTs(is);
|
||||
dts = readTs(is);
|
||||
NIOUtils.skip(is, header_len - 10);
|
||||
} else {
|
||||
NIOUtils.skip(is, header_len);
|
||||
}
|
||||
return new PESPacket(null, pts, streamId, len, pos, dts);
|
||||
}
|
||||
|
||||
public static long readTs(ByteBuffer is) {
|
||||
return ((long)is.get() & 0xEL) << 29L | (long)((is.get() & 0xFF) << 22) | (long)((is.get() & 0xFF) >> 1 << 15) |
|
||||
(long)((is.get() & 0xFF) << 7) | (long)((is.get() & 0xFF) >> 1);
|
||||
}
|
||||
|
||||
public static void writeTs(ByteBuffer is, long ts) {
|
||||
is.put((byte)(int)(ts >> 29L << 1L));
|
||||
is.put((byte)(int)(ts >> 22L));
|
||||
is.put((byte)(int)(ts >> 15L << 1L));
|
||||
is.put((byte)(int)(ts >> 7L));
|
||||
is.put((byte)(int)(ts >> 1L));
|
||||
}
|
||||
|
||||
public static class MPEGMediaDescriptor {
|
||||
private int tag;
|
||||
|
||||
private int len;
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
this.tag = buf.get() & 0xFF;
|
||||
this.len = buf.get() & 0xFF;
|
||||
}
|
||||
|
||||
public int getTag() {
|
||||
return this.tag;
|
||||
}
|
||||
|
||||
public int getLen() {
|
||||
return this.len;
|
||||
}
|
||||
}
|
||||
|
||||
public static class VideoStreamDescriptor extends MPEGMediaDescriptor {
|
||||
private int multipleFrameRate;
|
||||
|
||||
private int frameRateCode;
|
||||
|
||||
private boolean mpeg1Only;
|
||||
|
||||
private int constrainedParameter;
|
||||
|
||||
private int stillPicture;
|
||||
|
||||
private int profileAndLevel;
|
||||
|
||||
private int chromaFormat;
|
||||
|
||||
private int frameRateExtension;
|
||||
|
||||
Rational[] frameRates = new Rational[] {
|
||||
null, new Rational(24000, 1001), new Rational(24, 1), new Rational(25, 1), new Rational(30000, 1001), new Rational(30, 1), new Rational(50, 1), new Rational(60000, 1001), new Rational(60, 1), null,
|
||||
null, null, null, null, null, null };
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
super.parse(buf);
|
||||
int b0 = buf.get() & 0xFF;
|
||||
this.multipleFrameRate = b0 >> 7 & 0x1;
|
||||
this.frameRateCode = b0 >> 3 & 0xF;
|
||||
this.mpeg1Only = ((b0 >> 2 & 0x1) == 0);
|
||||
this.constrainedParameter = b0 >> 1 & 0x1;
|
||||
this.stillPicture = b0 & 0x1;
|
||||
if (!this.mpeg1Only) {
|
||||
this.profileAndLevel = buf.get() & 0xFF;
|
||||
int b1 = buf.get() & 0xFF;
|
||||
this.chromaFormat = b1 >> 6;
|
||||
this.frameRateExtension = b1 >> 5 & 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
public Rational getFrameRate() {
|
||||
return this.frameRates[this.frameRateCode];
|
||||
}
|
||||
|
||||
public int getMultipleFrameRate() {
|
||||
return this.multipleFrameRate;
|
||||
}
|
||||
|
||||
public int getFrameRateCode() {
|
||||
return this.frameRateCode;
|
||||
}
|
||||
|
||||
public boolean isMpeg1Only() {
|
||||
return this.mpeg1Only;
|
||||
}
|
||||
|
||||
public int getConstrainedParameter() {
|
||||
return this.constrainedParameter;
|
||||
}
|
||||
|
||||
public int getStillPicture() {
|
||||
return this.stillPicture;
|
||||
}
|
||||
|
||||
public int getProfileAndLevel() {
|
||||
return this.profileAndLevel;
|
||||
}
|
||||
|
||||
public int getChromaFormat() {
|
||||
return this.chromaFormat;
|
||||
}
|
||||
|
||||
public int getFrameRateExtension() {
|
||||
return this.frameRateExtension;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AudioStreamDescriptor extends MPEGMediaDescriptor {
|
||||
private int variableRateAudioIndicator;
|
||||
|
||||
private int freeFormatFlag;
|
||||
|
||||
private int id;
|
||||
|
||||
private int layer;
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
super.parse(buf);
|
||||
int b0 = buf.get() & 0xFF;
|
||||
this.freeFormatFlag = b0 >> 7 & 0x1;
|
||||
this.id = b0 >> 6 & 0x1;
|
||||
this.layer = b0 >> 5 & 0x3;
|
||||
this.variableRateAudioIndicator = b0 >> 3 & 0x1;
|
||||
}
|
||||
|
||||
public int getVariableRateAudioIndicator() {
|
||||
return this.variableRateAudioIndicator;
|
||||
}
|
||||
|
||||
public int getFreeFormatFlag() {
|
||||
return this.freeFormatFlag;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public int getLayer() {
|
||||
return this.layer;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ISO639LanguageDescriptor extends MPEGMediaDescriptor {
|
||||
private IntArrayList languageCodes = IntArrayList.createIntArrayList();
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
super.parse(buf);
|
||||
while (buf.remaining() >= 4)
|
||||
this.languageCodes.add(buf.getInt());
|
||||
}
|
||||
|
||||
public IntArrayList getLanguageCodes() {
|
||||
return this.languageCodes;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mpeg4VideoDescriptor extends MPEGMediaDescriptor {
|
||||
private int profileLevel;
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
super.parse(buf);
|
||||
this.profileLevel = buf.get() & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Mpeg4AudioDescriptor extends MPEGMediaDescriptor {
|
||||
private int profileLevel;
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
super.parse(buf);
|
||||
this.profileLevel = buf.get() & 0xFF;
|
||||
}
|
||||
|
||||
public int getProfileLevel() {
|
||||
return this.profileLevel;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AVCVideoDescriptor extends MPEGMediaDescriptor {
|
||||
private int profileIdc;
|
||||
|
||||
private int flags;
|
||||
|
||||
private int level;
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
super.parse(buf);
|
||||
this.profileIdc = buf.get() & 0xFF;
|
||||
this.flags = buf.get() & 0xFF;
|
||||
this.level = buf.get() & 0xFF;
|
||||
}
|
||||
|
||||
public int getProfileIdc() {
|
||||
return this.profileIdc;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return this.flags;
|
||||
}
|
||||
|
||||
public int getLevel() {
|
||||
return this.level;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AACAudioDescriptor extends MPEGMediaDescriptor {
|
||||
private int profile;
|
||||
|
||||
private int channel;
|
||||
|
||||
private int flags;
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
super.parse(buf);
|
||||
this.profile = buf.get() & 0xFF;
|
||||
this.channel = buf.get() & 0xFF;
|
||||
this.flags = buf.get() & 0xFF;
|
||||
}
|
||||
|
||||
public int getProfile() {
|
||||
return this.profile;
|
||||
}
|
||||
|
||||
public int getChannel() {
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return this.flags;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DataStreamAlignmentDescriptor extends MPEGMediaDescriptor {
|
||||
private int alignmentType;
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
super.parse(buf);
|
||||
this.alignmentType = buf.get() & 0xFF;
|
||||
}
|
||||
|
||||
public int getAlignmentType() {
|
||||
return this.alignmentType;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RegistrationDescriptor extends MPEGMediaDescriptor {
|
||||
private int formatIdentifier;
|
||||
|
||||
private IntArrayList additionalFormatIdentifiers = IntArrayList.createIntArrayList();
|
||||
|
||||
public void parse(ByteBuffer buf) {
|
||||
super.parse(buf);
|
||||
this.formatIdentifier = buf.getInt();
|
||||
while (buf.hasRemaining())
|
||||
this.additionalFormatIdentifiers.add(buf.get() & 0xFF);
|
||||
}
|
||||
|
||||
public int getFormatIdentifier() {
|
||||
return this.formatIdentifier;
|
||||
}
|
||||
|
||||
public IntArrayList getAdditionalFormatIdentifiers() {
|
||||
return this.additionalFormatIdentifiers;
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<? extends MPEGMediaDescriptor>[] dMapping = new Class<?>[256];
|
||||
|
||||
static {
|
||||
dMapping[2] = VideoStreamDescriptor.class;
|
||||
dMapping[3] = AudioStreamDescriptor.class;
|
||||
dMapping[6] = DataStreamAlignmentDescriptor.class;
|
||||
dMapping[5] = RegistrationDescriptor.class;
|
||||
dMapping[10] = ISO639LanguageDescriptor.class;
|
||||
dMapping[27] = Mpeg4VideoDescriptor.class;
|
||||
dMapping[28] = Mpeg4AudioDescriptor.class;
|
||||
dMapping[40] = AVCVideoDescriptor.class;
|
||||
dMapping[43] = AACAudioDescriptor.class;
|
||||
}
|
||||
|
||||
public static List<MPEGMediaDescriptor> parseDescriptors(ByteBuffer bb) {
|
||||
List<MPEGMediaDescriptor> result = new ArrayList<>();
|
||||
while (bb.remaining() >= 2) {
|
||||
ByteBuffer dup = bb.duplicate();
|
||||
int tag = dup.get() & 0xFF;
|
||||
int len = dup.get() & 0xFF;
|
||||
ByteBuffer descriptorBuffer = NIOUtils.read(bb, len + 2);
|
||||
if (dMapping[tag] != null)
|
||||
try {
|
||||
MPEGMediaDescriptor descriptor = dMapping[tag].newInstance();
|
||||
descriptor.parse(descriptorBuffer);
|
||||
result.add(descriptor);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.jcodec.common.IntObjectMap;
|
||||
import org.jcodec.common.Preconditions;
|
||||
import org.jcodec.common.UsedViaReflection;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
|
||||
public class MTSDemuxer {
|
||||
private SeekableByteChannel channel;
|
||||
|
||||
private Map<Integer, ProgramChannel> programs;
|
||||
|
||||
public Set<Integer> getPrograms() {
|
||||
return this.programs.keySet();
|
||||
}
|
||||
|
||||
public Set<Integer> findPrograms(SeekableByteChannel src) throws IOException {
|
||||
long rem = src.position();
|
||||
Set<Integer> guids = new HashSet<>();
|
||||
for (int i = 0; guids.size() == 0 || i < guids.size() * 500; i++) {
|
||||
MTSPacket pkt = readPacket(src);
|
||||
if (pkt == null)
|
||||
break;
|
||||
if (pkt.payload != null) {
|
||||
ByteBuffer payload = pkt.payload;
|
||||
if (!guids.contains(Integer.valueOf(pkt.pid)) && (payload.duplicate().getInt() & 0xFFFFFF00) == 256)
|
||||
guids.add(Integer.valueOf(pkt.pid));
|
||||
}
|
||||
}
|
||||
src.setPosition(rem);
|
||||
return guids;
|
||||
}
|
||||
|
||||
public MTSDemuxer(SeekableByteChannel src) throws IOException {
|
||||
this.channel = src;
|
||||
this.programs = new HashMap<>();
|
||||
for (Iterator<Integer> iterator = findPrograms(src).iterator(); iterator.hasNext(); ) {
|
||||
int pid = iterator.next();
|
||||
this.programs.put(Integer.valueOf(pid), new ProgramChannel(this));
|
||||
}
|
||||
src.setPosition(0L);
|
||||
}
|
||||
|
||||
public ReadableByteChannel getProgram(int pid) {
|
||||
return this.programs.get(Integer.valueOf(pid));
|
||||
}
|
||||
|
||||
private static class ProgramChannel implements ReadableByteChannel {
|
||||
private final MTSDemuxer demuxer;
|
||||
|
||||
private List<ByteBuffer> data;
|
||||
|
||||
private boolean closed;
|
||||
|
||||
public ProgramChannel(MTSDemuxer demuxer) {
|
||||
this.demuxer = demuxer;
|
||||
this.data = new ArrayList<>();
|
||||
}
|
||||
|
||||
public boolean isOpen() {
|
||||
return (!this.closed && this.demuxer.channel.isOpen());
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
this.closed = true;
|
||||
this.data.clear();
|
||||
}
|
||||
|
||||
public int read(ByteBuffer dst) throws IOException {
|
||||
int bytesRead = 0;
|
||||
while (dst.hasRemaining()) {
|
||||
while (this.data.size() == 0) {
|
||||
if (!this.demuxer.readAndDispatchNextTSPacket())
|
||||
return (bytesRead > 0) ? bytesRead : -1;
|
||||
}
|
||||
ByteBuffer first = this.data.get(0);
|
||||
int toRead = Math.min(dst.remaining(), first.remaining());
|
||||
dst.put(NIOUtils.read(first, toRead));
|
||||
if (!first.hasRemaining())
|
||||
this.data.remove(0);
|
||||
bytesRead += toRead;
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public void storePacket(MTSDemuxer.MTSPacket pkt) {
|
||||
if (this.closed)
|
||||
return;
|
||||
this.data.add(pkt.payload);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean readAndDispatchNextTSPacket() throws IOException {
|
||||
MTSPacket pkt = readPacket(this.channel);
|
||||
if (pkt == null)
|
||||
return false;
|
||||
ProgramChannel program = this.programs.get(Integer.valueOf(pkt.pid));
|
||||
if (program != null)
|
||||
program.storePacket(pkt);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static class MTSPacket {
|
||||
public ByteBuffer payload;
|
||||
|
||||
public boolean payloadStart;
|
||||
|
||||
public int pid;
|
||||
|
||||
public MTSPacket(int guid, boolean payloadStart, ByteBuffer payload) {
|
||||
this.pid = guid;
|
||||
this.payloadStart = payloadStart;
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
|
||||
public static MTSPacket readPacket(ReadableByteChannel channel) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(188);
|
||||
if (NIOUtils.readFromChannel(channel, buffer) != 188)
|
||||
return null;
|
||||
buffer.flip();
|
||||
return parsePacket(buffer);
|
||||
}
|
||||
|
||||
public static MTSPacket parsePacket(ByteBuffer buffer) {
|
||||
int marker = buffer.get() & 0xFF;
|
||||
Preconditions.checkState((71 == marker));
|
||||
int guidFlags = buffer.getShort();
|
||||
int guid = guidFlags & 0x1FFF;
|
||||
int payloadStart = guidFlags >> 14 & 0x1;
|
||||
int b0 = buffer.get() & 0xFF;
|
||||
int counter = b0 & 0xF;
|
||||
if ((b0 & 0x20) != 0) {
|
||||
int taken = 0;
|
||||
taken = (buffer.get() & 0xFF) + 1;
|
||||
NIOUtils.skip(buffer, taken - 1);
|
||||
}
|
||||
return new MTSPacket(guid, (payloadStart == 1), ((b0 & 0x10) != 0) ? buffer : null);
|
||||
}
|
||||
|
||||
@UsedViaReflection
|
||||
public static int probe(ByteBuffer b_) {
|
||||
ByteBuffer b = b_.duplicate();
|
||||
IntObjectMap<List<ByteBuffer>> streams = new IntObjectMap<>();
|
||||
try {
|
||||
while (true) {
|
||||
ByteBuffer sub = NIOUtils.read(b, 188);
|
||||
if (sub.remaining() < 188)
|
||||
break;
|
||||
MTSPacket tsPkt = parsePacket(sub);
|
||||
if (tsPkt == null)
|
||||
break;
|
||||
List<ByteBuffer> data = streams.get(tsPkt.pid);
|
||||
if (data == null) {
|
||||
data = new ArrayList<>();
|
||||
streams.put(tsPkt.pid, data);
|
||||
}
|
||||
if (tsPkt.payload != null)
|
||||
data.add(tsPkt.payload);
|
||||
}
|
||||
} catch (Throwable t) {}
|
||||
int maxScore = 0;
|
||||
int[] keys = streams.keys();
|
||||
for (int i : keys) {
|
||||
List<ByteBuffer> packets = streams.get(i);
|
||||
int score = MPSDemuxer.probe(NIOUtils.combineBuffers(packets));
|
||||
if (score > maxScore)
|
||||
maxScore = score + ((packets.size() > 20) ? 50 : 0);
|
||||
}
|
||||
return maxScore;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.jcodec.common.IntArrayList;
|
||||
import org.jcodec.common.IntIntMap;
|
||||
import org.jcodec.common.Preconditions;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.tools.MainUtils;
|
||||
import org.jcodec.containers.mps.psi.PATSection;
|
||||
import org.jcodec.containers.mps.psi.PMTSection;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class MTSDump extends MPSDump {
|
||||
private static final MainUtils.Flag DUMP_FROM = MainUtils.Flag.flag("dump-from", null, "Stop reading at timestamp");
|
||||
|
||||
private static final MainUtils.Flag STOP_AT = MainUtils.Flag.flag("stop-at", null, "Start dumping from timestamp");
|
||||
|
||||
private static final MainUtils.Flag[] ALL_FLAGS = new MainUtils.Flag[] { DUMP_FROM, STOP_AT };
|
||||
|
||||
private int guid;
|
||||
|
||||
private ByteBuffer buf;
|
||||
|
||||
private ByteBuffer tsBuf;
|
||||
|
||||
private int tsNo;
|
||||
|
||||
private int globalPayload;
|
||||
|
||||
private int[] payloads;
|
||||
|
||||
private int[] nums;
|
||||
|
||||
private int[] prevPayloads;
|
||||
|
||||
private int[] prevNums;
|
||||
|
||||
public MTSDump(ReadableByteChannel ch, int targetGuid) {
|
||||
super(ch);
|
||||
this.buf = ByteBuffer.allocate(192512);
|
||||
this.tsBuf = ByteBuffer.allocate(188);
|
||||
this.guid = targetGuid;
|
||||
this.buf.position(this.buf.limit());
|
||||
this.tsBuf.position(this.tsBuf.limit());
|
||||
}
|
||||
|
||||
public static void main2(String[] args) throws IOException {
|
||||
ReadableByteChannel ch = null;
|
||||
try {
|
||||
MainUtils.Cmd cmd = MainUtils.parseArguments(args, ALL_FLAGS);
|
||||
if (cmd.args.length < 1) {
|
||||
MainUtils.printHelp(ALL_FLAGS, Arrays.asList("file name", "guid"));
|
||||
return;
|
||||
}
|
||||
if (cmd.args.length == 1) {
|
||||
System.out.println("MTS programs:");
|
||||
dumpProgramPids(NIOUtils.readableChannel(new File(cmd.args[0])));
|
||||
return;
|
||||
}
|
||||
ch = NIOUtils.readableChannel(new File(cmd.args[0]));
|
||||
Long dumpAfterPts = cmd.getLongFlag(DUMP_FROM);
|
||||
Long stopPts = cmd.getLongFlag(STOP_AT);
|
||||
new MTSDump(ch, Integer.parseInt(cmd.args[1])).dump(dumpAfterPts, stopPts);
|
||||
} finally {
|
||||
NIOUtils.closeQuietly(ch);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpProgramPids(ReadableByteChannel readableFileChannel) throws IOException {
|
||||
Set<Integer> pids = new HashSet<>();
|
||||
ByteBuffer buf = ByteBuffer.allocate(1925120);
|
||||
readableFileChannel.read(buf);
|
||||
buf.flip();
|
||||
buf.limit(buf.limit() - buf.limit() % 188);
|
||||
int pmtPid = -1;
|
||||
while (buf.hasRemaining()) {
|
||||
ByteBuffer tsBuf = NIOUtils.read(buf, 188);
|
||||
Preconditions.checkState((71 == (tsBuf.get() & 0xFF)));
|
||||
int guidFlags = (tsBuf.get() & 0xFF) << 8 | tsBuf.get() & 0xFF;
|
||||
int guid = guidFlags & 0x1FFF;
|
||||
System.out.println(guid);
|
||||
if (guid != 0)
|
||||
pids.add(Integer.valueOf(guid));
|
||||
if (guid == 0 || guid == pmtPid) {
|
||||
int payloadStart = guidFlags >> 14 & 0x1;
|
||||
int b0 = tsBuf.get() & 0xFF;
|
||||
int counter = b0 & 0xF;
|
||||
int payloadOff = 0;
|
||||
if ((b0 & 0x20) != 0)
|
||||
NIOUtils.skip(tsBuf, tsBuf.get() & 0xFF);
|
||||
if (payloadStart == 1)
|
||||
NIOUtils.skip(tsBuf, tsBuf.get() & 0xFF);
|
||||
if (guid == 0) {
|
||||
PATSection pat = PATSection.parsePAT(tsBuf);
|
||||
IntIntMap programs = pat.getPrograms();
|
||||
pmtPid = programs.values()[0];
|
||||
printPat(pat);
|
||||
continue;
|
||||
}
|
||||
if (guid == pmtPid) {
|
||||
PMTSection pmt = PMTSection.parsePMT(tsBuf);
|
||||
printPmt(pmt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Integer pid : pids)
|
||||
System.out.println(pid);
|
||||
}
|
||||
|
||||
private static void printPat(PATSection pat) {
|
||||
IntIntMap programs = pat.getPrograms();
|
||||
System.out.print("PAT: ");
|
||||
int[] keys = programs.keys();
|
||||
for (int i : keys)
|
||||
System.out.print("" + i + ":" + i + ", ");
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
private static void printPmt(PMTSection pmt) {
|
||||
System.out.print("PMT: ");
|
||||
for (PMTSection.PMTStream pmtStream : pmt.getStreams()) {
|
||||
System.out.print("" + pmtStream.getPid() + ":" + pmtStream.getPid() + ", ");
|
||||
for (MPSUtils.MPEGMediaDescriptor descriptor : pmtStream.getDesctiptors())
|
||||
System.out.println(Platform.toJSON(descriptor));
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
protected void logPes(PESPacket pkt, int hdrSize, ByteBuffer payload) {
|
||||
System.out.println("" + pkt.streamId + "(" + pkt.streamId + ") [ts#" + (
|
||||
(pkt.streamId >= 224) ? "video" : "audio") + ", " + mapPos(pkt.pos) + "b], pts: " +
|
||||
payload.remaining() + hdrSize + ", dts: " + pkt.pts);
|
||||
}
|
||||
|
||||
private int mapPos(long pos) {
|
||||
int left = this.globalPayload;
|
||||
for (int i = this.payloads.length - 1; i >= 0; i--) {
|
||||
left -= this.payloads[i];
|
||||
if ((long)left <= pos)
|
||||
return this.nums[i];
|
||||
}
|
||||
if (this.prevPayloads != null)
|
||||
for (int j = this.prevPayloads.length - 1; j >= 0; j--) {
|
||||
left -= this.prevPayloads[j];
|
||||
if ((long)left <= pos)
|
||||
return this.prevNums[j];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int fillBuffer(ByteBuffer dst) throws IOException {
|
||||
IntArrayList payloads = IntArrayList.createIntArrayList();
|
||||
IntArrayList nums = IntArrayList.createIntArrayList();
|
||||
int remaining = dst.remaining();
|
||||
try {
|
||||
dst.put(NIOUtils.read(this.tsBuf, Math.min(dst.remaining(), this.tsBuf.remaining())));
|
||||
while (dst.hasRemaining()) {
|
||||
if (!this.buf.hasRemaining()) {
|
||||
ByteBuffer dub = this.buf.duplicate();
|
||||
dub.clear();
|
||||
int read = this.ch.read(dub);
|
||||
if (read == -1)
|
||||
return (dst.remaining() != remaining) ? (remaining - dst.remaining()) : -1;
|
||||
dub.flip();
|
||||
dub.limit(dub.limit() - dub.limit() % 188);
|
||||
this.buf = dub;
|
||||
}
|
||||
this.tsBuf = NIOUtils.read(this.buf, 188);
|
||||
Preconditions.checkState((71 == (this.tsBuf.get() & 0xFF)));
|
||||
this.tsNo++;
|
||||
int guidFlags = (this.tsBuf.get() & 0xFF) << 8 | this.tsBuf.get() & 0xFF;
|
||||
int guid = guidFlags & 0x1FFF;
|
||||
if (guid != this.guid)
|
||||
continue;
|
||||
int payloadStart = guidFlags >> 14 & 0x1;
|
||||
int b0 = this.tsBuf.get() & 0xFF;
|
||||
int counter = b0 & 0xF;
|
||||
if ((b0 & 0x20) != 0)
|
||||
NIOUtils.skip(this.tsBuf, this.tsBuf.get() & 0xFF);
|
||||
this.globalPayload += this.tsBuf.remaining();
|
||||
payloads.add(this.tsBuf.remaining());
|
||||
nums.add(this.tsNo - 1);
|
||||
dst.put(NIOUtils.read(this.tsBuf, Math.min(dst.remaining(), this.tsBuf.remaining())));
|
||||
}
|
||||
} finally {
|
||||
this.prevPayloads = this.payloads;
|
||||
this.payloads = payloads.toArray();
|
||||
this.prevNums = this.nums;
|
||||
this.nums = nums.toArray();
|
||||
}
|
||||
return remaining - dst.remaining();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import org.jcodec.common.IntIntMap;
|
||||
import org.jcodec.common.Preconditions;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.tools.MainUtils;
|
||||
import org.jcodec.containers.mps.psi.PATSection;
|
||||
import org.jcodec.containers.mps.psi.PMTSection;
|
||||
|
||||
public class MTSPktDump {
|
||||
public static void main1(String[] args) throws IOException {
|
||||
MainUtils.Cmd cmd = MainUtils.parseArguments(args, new MainUtils.Flag[0]);
|
||||
if (cmd.args.length < 1) {
|
||||
MainUtils.printHelpNoFlags(new String[] { "file name" });
|
||||
return;
|
||||
}
|
||||
ReadableByteChannel ch = null;
|
||||
try {
|
||||
ch = NIOUtils.readableChannel(new File(cmd.args[0]));
|
||||
dumpTSPackets(ch);
|
||||
} finally {
|
||||
NIOUtils.closeQuietly(ch);
|
||||
}
|
||||
}
|
||||
|
||||
private static void dumpTSPackets(ReadableByteChannel _in) throws IOException {
|
||||
ByteBuffer buf = ByteBuffer.allocate(192512);
|
||||
while (_in.read(buf) != -1) {
|
||||
buf.flip();
|
||||
buf.limit(buf.limit() / 188 * 188);
|
||||
int pmtPid = -1;
|
||||
for (int pkt = 0; buf.hasRemaining(); pkt++) {
|
||||
ByteBuffer tsBuf = NIOUtils.read(buf, 188);
|
||||
Preconditions.checkState((71 == (tsBuf.get() & 0xFF)));
|
||||
int guidFlags = (tsBuf.get() & 0xFF) << 8 | tsBuf.get() & 0xFF;
|
||||
int guid = guidFlags & 0x1FFF;
|
||||
int payloadStart = guidFlags >> 14 & 0x1;
|
||||
int b0 = tsBuf.get() & 0xFF;
|
||||
int counter = b0 & 0xF;
|
||||
if ((b0 & 0x20) != 0)
|
||||
NIOUtils.skip(tsBuf, tsBuf.get() & 0xFF);
|
||||
System.out.print("#" + pkt + "[guid: " + guid + ", cnt: " + counter + ", start: " + (
|
||||
(payloadStart == 1) ? "y" : "-"));
|
||||
if (guid == 0 || guid == pmtPid) {
|
||||
System.out.print(", PSI]: ");
|
||||
if (payloadStart == 1)
|
||||
NIOUtils.skip(tsBuf, tsBuf.get() & 0xFF);
|
||||
if (guid == 0) {
|
||||
PATSection pat = PATSection.parsePAT(tsBuf);
|
||||
IntIntMap programs = pat.getPrograms();
|
||||
pmtPid = programs.values()[0];
|
||||
printPat(pat);
|
||||
} else if (guid == pmtPid) {
|
||||
PMTSection pmt = PMTSection.parsePMT(tsBuf);
|
||||
printPmt(pmt);
|
||||
}
|
||||
} else {
|
||||
System.out.print("]: " + tsBuf.remaining());
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static void printPat(PATSection pat) {
|
||||
IntIntMap programs = pat.getPrograms();
|
||||
System.out.print("PAT: ");
|
||||
int[] keys = programs.keys();
|
||||
for (int i : keys)
|
||||
System.out.print("" + i + ":" + i + ", ");
|
||||
}
|
||||
|
||||
private static void printPmt(PMTSection pmt) {
|
||||
System.out.print("PMT: ");
|
||||
for (PMTSection.PMTStream pmtStream : pmt.getStreams())
|
||||
System.out.print("" + pmtStream.getPid() + ":" + pmtStream.getPid() + ", ");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import org.jcodec.common.IntIntMap;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.tools.MainUtils;
|
||||
import org.jcodec.containers.mps.psi.PATSection;
|
||||
import org.jcodec.containers.mps.psi.PSISection;
|
||||
|
||||
public class MTSReplacePid extends MTSUtils.TSReader {
|
||||
private Set<Integer> pmtPids;
|
||||
|
||||
private IntIntMap replaceSpec;
|
||||
|
||||
public MTSReplacePid(IntIntMap replaceSpec) {
|
||||
super(true);
|
||||
this.pmtPids = new HashSet<>();
|
||||
this.replaceSpec = replaceSpec;
|
||||
}
|
||||
|
||||
public boolean onPkt(int guid, boolean payloadStart, ByteBuffer tsBuf, long filePos, boolean sectionSyntax, ByteBuffer fullPkt) {
|
||||
if (sectionSyntax) {
|
||||
replaceRefs(this.replaceSpec, guid, tsBuf, this.pmtPids);
|
||||
} else {
|
||||
System.out.print("TS ");
|
||||
ByteBuffer buf = fullPkt.duplicate();
|
||||
short tsFlags = buf.getShort(buf.position() + 1);
|
||||
buf.putShort(buf.position() + 1, (short)(replacePid(this.replaceSpec, tsFlags & 0x1FFF) | tsFlags & 0xFFFFE000));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IntIntMap parseReplaceSpec(String spec) {
|
||||
IntIntMap map = new IntIntMap();
|
||||
for (String pidPair : spec.split(",")) {
|
||||
String[] pidPairParsed = pidPair.split(":");
|
||||
map.put(Integer.parseInt(pidPairParsed[0]), Integer.parseInt(pidPairParsed[1]));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private void replaceRefs(IntIntMap replaceSpec, int guid, ByteBuffer buf, Set<Integer> pmtPids) {
|
||||
if (guid == 0) {
|
||||
PATSection pat = PATSection.parsePAT(buf);
|
||||
for (int pids : pat.getPrograms().values())
|
||||
pmtPids.add(Integer.valueOf(pids));
|
||||
} else if (pmtPids.contains(Integer.valueOf(guid))) {
|
||||
System.out.println(MainUtils.bold("PMT"));
|
||||
PSISection.parsePSI(buf);
|
||||
buf.getShort();
|
||||
NIOUtils.skip(buf, buf.getShort() & 0xFFF);
|
||||
while (buf.remaining() > 4) {
|
||||
byte streamType = buf.get();
|
||||
MTSStreamType fromTag = MTSStreamType.fromTag(streamType);
|
||||
System.out.print(String.valueOf((fromTag == null) ? "UNKNOWN" : fromTag) + "(" + String.valueOf((fromTag == null) ? "UNKNOWN" : fromTag) + "):\t");
|
||||
int wn = buf.getShort() & 0xFFFF;
|
||||
int wasPid = wn & 0x1FFF;
|
||||
int elementaryPid = replacePid(replaceSpec, wasPid);
|
||||
buf.putShort(buf.position() - 2, (short)(elementaryPid & 0x1FFF | wn & 0xFFFFE000));
|
||||
NIOUtils.skip(buf, buf.getShort() & 0xFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int replacePid(IntIntMap replaceSpec, int pid) {
|
||||
int newPid = pid;
|
||||
if (replaceSpec.contains(pid))
|
||||
newPid = replaceSpec.get(pid);
|
||||
System.out.println("[" + pid + "->" + newPid + "]");
|
||||
return newPid;
|
||||
}
|
||||
|
||||
public static void main1(String[] args) throws IOException {
|
||||
MainUtils.Cmd cmd = MainUtils.parseArguments(args, new MainUtils.Flag[0]);
|
||||
if (cmd.args.length < 2) {
|
||||
MainUtils.printHelpNoFlags(new String[] { "pid_from:pid_to,[pid_from:pid_to...]", "file" });
|
||||
return;
|
||||
}
|
||||
IntIntMap replaceSpec = parseReplaceSpec(cmd.getArg(0));
|
||||
SeekableByteChannel ch = null;
|
||||
try {
|
||||
ch = NIOUtils.rwChannel(new File(cmd.getArg(1)));
|
||||
new MTSReplacePid(replaceSpec).readTsFile(ch);
|
||||
} finally {
|
||||
NIOUtils.closeQuietly(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class MTSStreamType {
|
||||
private static final List<MTSStreamType> _values = new ArrayList<>();
|
||||
|
||||
public static final MTSStreamType RESERVED = new MTSStreamType(0, false, false);
|
||||
|
||||
public static final MTSStreamType VIDEO_MPEG1 = new MTSStreamType(1, true, false);
|
||||
|
||||
public static final MTSStreamType VIDEO_MPEG2 = new MTSStreamType(2, true, false);
|
||||
|
||||
public static final MTSStreamType AUDIO_MPEG1 = new MTSStreamType(3, false, true);
|
||||
|
||||
public static final MTSStreamType AUDIO_MPEG2 = new MTSStreamType(4, false, true);
|
||||
|
||||
public static final MTSStreamType PRIVATE_SECTION = new MTSStreamType(5, false, false);
|
||||
|
||||
public static final MTSStreamType PRIVATE_DATA = new MTSStreamType(6, false, false);
|
||||
|
||||
public static final MTSStreamType MHEG = new MTSStreamType(7, false, false);
|
||||
|
||||
public static final MTSStreamType DSM_CC = new MTSStreamType(8, false, false);
|
||||
|
||||
public static final MTSStreamType ATM_SYNC = new MTSStreamType(9, false, false);
|
||||
|
||||
public static final MTSStreamType DSM_CC_A = new MTSStreamType(10, false, false);
|
||||
|
||||
public static final MTSStreamType DSM_CC_B = new MTSStreamType(11, false, false);
|
||||
|
||||
public static final MTSStreamType DSM_CC_C = new MTSStreamType(12, false, false);
|
||||
|
||||
public static final MTSStreamType DSM_CC_D = new MTSStreamType(13, false, false);
|
||||
|
||||
public static final MTSStreamType MPEG_AUX = new MTSStreamType(14, false, false);
|
||||
|
||||
public static final MTSStreamType AUDIO_AAC_ADTS = new MTSStreamType(15, false, true);
|
||||
|
||||
public static final MTSStreamType VIDEO_MPEG4 = new MTSStreamType(16, true, false);
|
||||
|
||||
public static final MTSStreamType AUDIO_AAC_LATM = new MTSStreamType(17, false, true);
|
||||
|
||||
public static final MTSStreamType FLEXMUX_PES = new MTSStreamType(18, false, false);
|
||||
|
||||
public static final MTSStreamType FLEXMUX_SEC = new MTSStreamType(19, false, false);
|
||||
|
||||
public static final MTSStreamType DSM_CC_SDP = new MTSStreamType(20, false, false);
|
||||
|
||||
public static final MTSStreamType META_PES = new MTSStreamType(21, false, false);
|
||||
|
||||
public static final MTSStreamType META_SEC = new MTSStreamType(22, false, false);
|
||||
|
||||
public static final MTSStreamType DSM_CC_DATA_CAROUSEL = new MTSStreamType(23, false, false);
|
||||
|
||||
public static final MTSStreamType DSM_CC_OBJ_CAROUSEL = new MTSStreamType(24, false, false);
|
||||
|
||||
public static final MTSStreamType DSM_CC_SDP1 = new MTSStreamType(25, false, false);
|
||||
|
||||
public static final MTSStreamType IPMP = new MTSStreamType(26, false, false);
|
||||
|
||||
public static final MTSStreamType VIDEO_H264 = new MTSStreamType(27, true, false);
|
||||
|
||||
public static final MTSStreamType AUDIO_AAC_RAW = new MTSStreamType(28, false, true);
|
||||
|
||||
public static final MTSStreamType SUBS = new MTSStreamType(29, false, false);
|
||||
|
||||
public static final MTSStreamType AUX_3D = new MTSStreamType(30, false, false);
|
||||
|
||||
public static final MTSStreamType VIDEO_AVC_SVC = new MTSStreamType(31, true, false);
|
||||
|
||||
public static final MTSStreamType VIDEO_AVC_MVC = new MTSStreamType(32, true, false);
|
||||
|
||||
public static final MTSStreamType VIDEO_J2K = new MTSStreamType(33, true, false);
|
||||
|
||||
public static final MTSStreamType VIDEO_MPEG2_3D = new MTSStreamType(34, true, false);
|
||||
|
||||
public static final MTSStreamType VIDEO_H264_3D = new MTSStreamType(35, true, false);
|
||||
|
||||
public static final MTSStreamType VIDEO_CAVS = new MTSStreamType(66, false, true);
|
||||
|
||||
public static final MTSStreamType IPMP_STREAM = new MTSStreamType(127, false, false);
|
||||
|
||||
public static final MTSStreamType AUDIO_AC3 = new MTSStreamType(129, false, true);
|
||||
|
||||
public static final MTSStreamType AUDIO_DTS = new MTSStreamType(138, false, true);
|
||||
|
||||
private int tag;
|
||||
|
||||
private boolean video;
|
||||
|
||||
private boolean audio;
|
||||
|
||||
private MTSStreamType(int tag, boolean video, boolean audio) {
|
||||
this.tag = tag;
|
||||
this.video = video;
|
||||
this.audio = audio;
|
||||
_values.add(this);
|
||||
}
|
||||
|
||||
public static MTSStreamType[] values() {
|
||||
return _values.<MTSStreamType>toArray(new MTSStreamType[0]);
|
||||
}
|
||||
|
||||
public static MTSStreamType fromTag(int streamTypeTag) {
|
||||
MTSStreamType[] values = values();
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
MTSStreamType streamType = values[i];
|
||||
if (streamType.tag == streamTypeTag)
|
||||
return streamType;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getTag() {
|
||||
return this.tag;
|
||||
}
|
||||
|
||||
public boolean isVideo() {
|
||||
return this.video;
|
||||
}
|
||||
|
||||
public boolean isAudio() {
|
||||
return this.audio;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.IntArrayList;
|
||||
import org.jcodec.common.Preconditions;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mps.psi.PATSection;
|
||||
import org.jcodec.containers.mps.psi.PMTSection;
|
||||
import org.jcodec.containers.mps.psi.PSISection;
|
||||
|
||||
public class MTSUtils {
|
||||
@Deprecated
|
||||
public static int parsePAT(ByteBuffer data) {
|
||||
PATSection pat = PATSection.parsePAT(data);
|
||||
if (pat.getPrograms().size() > 0)
|
||||
return pat.getPrograms().values()[0];
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static PMTSection parsePMT(ByteBuffer data) {
|
||||
return PMTSection.parsePMT(data);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static PSISection parseSection(ByteBuffer data) {
|
||||
return PSISection.parsePSI(data);
|
||||
}
|
||||
|
||||
private static void parseEsInfo(ByteBuffer read) {}
|
||||
|
||||
public static PMTSection.PMTStream[] getProgramGuids(File src) throws IOException {
|
||||
SeekableByteChannel ch = null;
|
||||
try {
|
||||
ch = NIOUtils.readableChannel(src);
|
||||
return getProgramGuidsFromChannel(ch);
|
||||
} finally {
|
||||
NIOUtils.closeQuietly(ch);
|
||||
}
|
||||
}
|
||||
|
||||
public static PMTSection.PMTStream[] getProgramGuidsFromChannel(SeekableByteChannel _in) throws IOException {
|
||||
PMTExtractor ex = new PMTExtractor();
|
||||
ex.readTsFile(_in);
|
||||
PMTSection pmt = ex.getPmt();
|
||||
return pmt.getStreams();
|
||||
}
|
||||
|
||||
private static class PMTExtractor extends TSReader {
|
||||
public PMTExtractor() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
private int pmtGuid = -1;
|
||||
|
||||
private PMTSection pmt;
|
||||
|
||||
public boolean onPkt(int guid, boolean payloadStart, ByteBuffer tsBuf, long filePos, boolean sectionSyntax, ByteBuffer fullPkt) {
|
||||
if (guid == 0) {
|
||||
this.pmtGuid = MTSUtils.parsePAT(tsBuf);
|
||||
} else if (this.pmtGuid != -1 && guid == this.pmtGuid) {
|
||||
this.pmt = MTSUtils.parsePMT(tsBuf);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public PMTSection getPmt() {
|
||||
return this.pmt;
|
||||
}
|
||||
}
|
||||
|
||||
public static abstract class TSReader {
|
||||
private static final int TS_SYNC_MARKER = 71;
|
||||
|
||||
private static final int TS_PKT_SIZE = 188;
|
||||
|
||||
public static final int BUFFER_SIZE = 96256;
|
||||
|
||||
private boolean flush;
|
||||
|
||||
public TSReader(boolean flush) {
|
||||
this.flush = flush;
|
||||
}
|
||||
|
||||
public void readTsFile(SeekableByteChannel ch) throws IOException {
|
||||
ch.setPosition(0L);
|
||||
ByteBuffer buf = ByteBuffer.allocate(96256);
|
||||
for (long pos = ch.position(); ch.read(buf) >= 188; pos = ch.position()) {
|
||||
long posRem = pos;
|
||||
buf.flip();
|
||||
while (buf.remaining() >= 188) {
|
||||
ByteBuffer tsBuf = NIOUtils.read(buf, 188);
|
||||
ByteBuffer fullPkt = tsBuf.duplicate();
|
||||
pos += 188L;
|
||||
Preconditions.checkState((71 == (tsBuf.get() & 0xFF)));
|
||||
int guidFlags = (tsBuf.get() & 0xFF) << 8 | tsBuf.get() & 0xFF;
|
||||
int guid = guidFlags & 0x1FFF;
|
||||
int payloadStart = guidFlags >> 14 & 0x1;
|
||||
int b0 = tsBuf.get() & 0xFF;
|
||||
int counter = b0 & 0xF;
|
||||
if ((b0 & 0x20) != 0)
|
||||
NIOUtils.skip(tsBuf, tsBuf.get() & 0xFF);
|
||||
boolean sectionSyntax = (payloadStart == 1 && (NIOUtils.getRel(tsBuf, NIOUtils.getRel(tsBuf, 0) + 2) & 0x80) == 128);
|
||||
if (sectionSyntax)
|
||||
NIOUtils.skip(tsBuf, tsBuf.get() & 0xFF);
|
||||
if (!onPkt(guid, (payloadStart == 1), tsBuf, pos - (long)tsBuf.remaining(), sectionSyntax, fullPkt))
|
||||
return;
|
||||
}
|
||||
if (this.flush) {
|
||||
buf.flip();
|
||||
ch.setPosition(posRem);
|
||||
ch.write(buf);
|
||||
}
|
||||
buf.clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean onPkt(int guid, boolean payloadStart, ByteBuffer tsBuf, long filePos, boolean sectionSyntax, ByteBuffer fullPkt) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getVideoPid(File src) throws IOException {
|
||||
for (PMTSection.PMTStream stream : getProgramGuids(src)) {
|
||||
if (stream.getStreamType().isVideo())
|
||||
return stream.getPid();
|
||||
}
|
||||
throw new RuntimeException("No video stream");
|
||||
}
|
||||
|
||||
public static int getAudioPid(File src) throws IOException {
|
||||
for (PMTSection.PMTStream stream : getProgramGuids(src)) {
|
||||
if (stream.getStreamType().isAudio())
|
||||
return stream.getPid();
|
||||
}
|
||||
throw new RuntimeException("No audio stream");
|
||||
}
|
||||
|
||||
public static int[] getMediaPidsFromChannel(SeekableByteChannel src) throws IOException {
|
||||
return filterMediaPids(getProgramGuidsFromChannel(src));
|
||||
}
|
||||
|
||||
public static int[] getMediaPids(File src) throws IOException {
|
||||
return filterMediaPids(getProgramGuids(src));
|
||||
}
|
||||
|
||||
private static int[] filterMediaPids(PMTSection.PMTStream[] programs) {
|
||||
IntArrayList result = IntArrayList.createIntArrayList();
|
||||
for (PMTSection.PMTStream stream : programs) {
|
||||
if (stream.getStreamType().isVideo() || stream.getStreamType().isAudio())
|
||||
result.add(stream.getPid());
|
||||
}
|
||||
return result.toArray();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package org.jcodec.containers.mps;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PESPacket {
|
||||
public ByteBuffer data;
|
||||
|
||||
public long pts;
|
||||
|
||||
public int streamId;
|
||||
|
||||
public int length;
|
||||
|
||||
public long pos;
|
||||
|
||||
public long dts;
|
||||
|
||||
public PESPacket(ByteBuffer data, long pts, int streamId, int length, long pos, long dts) {
|
||||
this.data = data;
|
||||
this.pts = pts;
|
||||
this.streamId = streamId;
|
||||
this.length = length;
|
||||
this.pos = pos;
|
||||
this.dts = dts;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
package org.jcodec.containers.mps.index;
|
||||
|
||||
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.Set;
|
||||
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;
|
||||
|
||||
public abstract class BaseIndexer extends MPSUtils.PESReader {
|
||||
private Map<Integer, BaseAnalyser> analyzers = new HashMap<>();
|
||||
|
||||
private LongArrayList tokens = LongArrayList.createLongArrayList();
|
||||
|
||||
private RunLength.Integer streams = new RunLength.Integer();
|
||||
|
||||
public int estimateSize() {
|
||||
int sizeEstimate = (this.tokens.size() << 3) + this.streams.estimateSize() + 128;
|
||||
for (Integer stream : this.analyzers.keySet())
|
||||
sizeEstimate += this.analyzers.get(stream).estimateSize();
|
||||
return sizeEstimate;
|
||||
}
|
||||
|
||||
protected static abstract class BaseAnalyser {
|
||||
protected IntArrayList pts = new IntArrayList(250000);
|
||||
|
||||
protected IntArrayList dur = new IntArrayList(250000);
|
||||
|
||||
public abstract void pkt(ByteBuffer param1ByteBuffer, PESPacket param1PESPacket);
|
||||
|
||||
public abstract void finishAnalyse();
|
||||
|
||||
public int estimateSize() {
|
||||
return (this.pts.size() << 2) + 4;
|
||||
}
|
||||
|
||||
public abstract MPSIndex.MPSStreamIndex serialize(int param1Int);
|
||||
}
|
||||
|
||||
private static class GenericAnalyser extends BaseAnalyser {
|
||||
private IntArrayList sizes = new IntArrayList(250000);
|
||||
|
||||
private int knownDuration;
|
||||
|
||||
private long lastPts;
|
||||
|
||||
public void pkt(ByteBuffer pkt, PESPacket pesHeader) {
|
||||
this.sizes.add(pkt.remaining());
|
||||
if (pesHeader.pts == -1L) {
|
||||
pesHeader.pts = this.lastPts + (long)this.knownDuration;
|
||||
} else {
|
||||
this.knownDuration = (int)(pesHeader.pts - this.lastPts);
|
||||
this.lastPts = pesHeader.pts;
|
||||
}
|
||||
this.pts.add((int)pesHeader.pts);
|
||||
this.dur.add(this.knownDuration);
|
||||
}
|
||||
|
||||
public MPSIndex.MPSStreamIndex serialize(int streamId) {
|
||||
return new MPSIndex.MPSStreamIndex(streamId, this.sizes.toArray(), this.pts.toArray(), this.dur.toArray(), new int[0]);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return super.estimateSize() + (this.sizes.size() << 2) + 32;
|
||||
}
|
||||
|
||||
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<Frame> curGop;
|
||||
|
||||
private long phPos = -1L;
|
||||
|
||||
private Frame lastFrameOfLastGop;
|
||||
|
||||
public MPEGVideoAnalyser() {
|
||||
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;
|
||||
this.position++;
|
||||
this.marker = this.marker << 8 | b;
|
||||
if (this.phPos != -1L) {
|
||||
long phOffset = this.position - this.phPos;
|
||||
if (phOffset == 5L) {
|
||||
this.lastFrame.tempRef = b << 2;
|
||||
} else if (phOffset == 6L) {
|
||||
int picCodingType = b >> 3 & 0x7;
|
||||
this.lastFrame.tempRef |= b >> 6;
|
||||
if (picCodingType == 1) {
|
||||
this.keyFrames.add(this.frameNo - 1);
|
||||
if (this.curGop.size() > 0)
|
||||
outGop();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((this.marker & 0xFFFFFF00) != 256)
|
||||
continue;
|
||||
if (this.inFrameData && (this.marker == 256 || this.marker > 431)) {
|
||||
this.lastFrame.size = (int)(this.position - 4L - this.lastFrame.offset);
|
||||
this.curGop.add(this.lastFrame);
|
||||
this.lastFrame = null;
|
||||
this.inFrameData = false;
|
||||
} else if (!this.inFrameData && this.marker > 256 && this.marker <= 431) {
|
||||
this.inFrameData = true;
|
||||
}
|
||||
if (this.lastFrame == null && (this.marker == 435 || this.marker == 440 || this.marker == 256)) {
|
||||
Frame frame = new Frame();
|
||||
frame.pts = (int)pesHeader.pts;
|
||||
frame.offset = this.position - 4L;
|
||||
Logger.info(String.format("FRAME[%d]: %012x, %d", this.frameNo, pesHeader.pos + (long)pkt.position() - 4L, pesHeader.pts));
|
||||
this.frameNo++;
|
||||
this.lastFrame = frame;
|
||||
}
|
||||
if (this.lastFrame != null && this.lastFrame.pts == -1 && this.marker == 256)
|
||||
this.lastFrame.pts = (int)pesHeader.pts;
|
||||
this.phPos = (this.marker == 256) ? (this.position - 4L) : -1L;
|
||||
}
|
||||
}
|
||||
|
||||
private void outGop() {
|
||||
fixPts(this.curGop);
|
||||
for (Frame frame : this.curGop) {
|
||||
this.sizes.add(frame.size);
|
||||
this.pts.add(frame.pts);
|
||||
}
|
||||
this.curGop.clear();
|
||||
}
|
||||
|
||||
private void fixPts(List<Frame> curGop) {
|
||||
Frame[] frames = curGop.<Frame>toArray(new Frame[0]);
|
||||
Arrays.sort(frames, new Comparator<>() {
|
||||
public int compare(BaseIndexer.MPEGVideoAnalyser.Frame o1, BaseIndexer.MPEGVideoAnalyser.Frame o2) {
|
||||
return (o1.tempRef > o2.tempRef) ? 1 : ((o1.tempRef == o2.tempRef) ? 0 : -1);
|
||||
}
|
||||
});
|
||||
for (int dir = 0; dir < 3; dir++) {
|
||||
for (int j = 0, lastPts = -1, secondLastPts = -1, lastTref = -1, secondLastTref = -1; j < frames.length; j++) {
|
||||
if ((frames[j]).pts == -1 && lastPts != -1 && secondLastPts != -1)
|
||||
(frames[j]).pts = lastPts + (lastPts - secondLastPts) / MathUtil.abs(lastTref - secondLastTref);
|
||||
if ((frames[j]).pts != -1) {
|
||||
secondLastPts = lastPts;
|
||||
secondLastTref = lastTref;
|
||||
lastPts = (frames[j]).pts;
|
||||
lastTref = (frames[j]).tempRef;
|
||||
}
|
||||
}
|
||||
ArrayUtil.reverse(frames);
|
||||
}
|
||||
if (this.lastFrameOfLastGop != null)
|
||||
this.dur.add((frames[0]).pts - this.lastFrameOfLastGop.pts);
|
||||
for (int i = 1; i < frames.length; i++)
|
||||
this.dur.add((frames[i]).pts - (frames[i - 1]).pts);
|
||||
this.lastFrameOfLastGop = frames[frames.length - 1];
|
||||
}
|
||||
|
||||
public void finishAnalyse() {
|
||||
if (this.lastFrame == null)
|
||||
return;
|
||||
this.lastFrame.size = (int)(this.position - this.lastFrame.offset);
|
||||
this.curGop.add(this.lastFrame);
|
||||
outGop();
|
||||
}
|
||||
|
||||
public MPSIndex.MPSStreamIndex serialize(int streamId) {
|
||||
return new MPSIndex.MPSStreamIndex(streamId, this.sizes.toArray(), this.pts.toArray(), this.dur.toArray(), this.keyFrames.toArray());
|
||||
}
|
||||
}
|
||||
|
||||
private static class Frame {
|
||||
long offset;
|
||||
|
||||
int size;
|
||||
|
||||
int pts;
|
||||
|
||||
int tempRef;
|
||||
}
|
||||
|
||||
class null implements Comparator<MPEGVideoAnalyser.Frame> {
|
||||
public int compare(BaseIndexer.MPEGVideoAnalyser.Frame o1, BaseIndexer.MPEGVideoAnalyser.Frame o2) {
|
||||
return (o1.tempRef > o2.tempRef) ? 1 : ((o1.tempRef == o2.tempRef) ? 0 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
protected BaseAnalyser getAnalyser(int stream) {
|
||||
BaseAnalyser analizer = this.analyzers.get(Integer.valueOf(stream));
|
||||
if (analizer == null) {
|
||||
analizer = (stream >= 224 && stream <= 239) ? new MPEGVideoAnalyser() : new GenericAnalyser();
|
||||
this.analyzers.put(Integer.valueOf(stream), analizer);
|
||||
}
|
||||
return this.analyzers.get(Integer.valueOf(stream));
|
||||
}
|
||||
|
||||
public MPSIndex serialize() {
|
||||
List<MPSIndex.MPSStreamIndex> streamsIndices = new ArrayList<>();
|
||||
Set<Map.Entry<Integer, BaseAnalyser>> entrySet = this.analyzers.entrySet();
|
||||
for (Map.Entry<Integer, BaseAnalyser> entry : entrySet)
|
||||
streamsIndices.add(entry.getValue().serialize(entry.getKey().intValue()));
|
||||
return new MPSIndex(this.tokens.toArray(), this.streams, streamsIndices.<MPSIndex.MPSStreamIndex>toArray(new MPSIndex.MPSStreamIndex[0]));
|
||||
}
|
||||
|
||||
protected void savePESMeta(int stream, long token) {
|
||||
this.tokens.add(token);
|
||||
this.streams.add(stream);
|
||||
}
|
||||
|
||||
void finishAnalyse() {
|
||||
finishRead();
|
||||
for (BaseAnalyser baseAnalyser : this.analyzers.values())
|
||||
baseAnalyser.finishAnalyse();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
package org.jcodec.containers.mps.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.RunLength;
|
||||
|
||||
public class MPSIndex {
|
||||
protected long[] pesTokens;
|
||||
|
||||
protected RunLength.Integer pesStreamIds;
|
||||
|
||||
protected MPSStreamIndex[] streams;
|
||||
|
||||
public static class MPSStreamIndex {
|
||||
protected int streamId;
|
||||
|
||||
protected int[] fsizes;
|
||||
|
||||
protected int[] fpts;
|
||||
|
||||
protected int[] fdur;
|
||||
|
||||
protected int[] sync;
|
||||
|
||||
public MPSStreamIndex(int streamId, int[] fsizes, int[] fpts, int[] fdur, int[] sync) {
|
||||
this.streamId = streamId;
|
||||
this.fsizes = fsizes;
|
||||
this.fpts = fpts;
|
||||
this.fdur = fdur;
|
||||
this.sync = sync;
|
||||
}
|
||||
|
||||
public int getStreamId() {
|
||||
return this.streamId;
|
||||
}
|
||||
|
||||
public int[] getFsizes() {
|
||||
return this.fsizes;
|
||||
}
|
||||
|
||||
public int[] getFpts() {
|
||||
return this.fpts;
|
||||
}
|
||||
|
||||
public int[] getFdur() {
|
||||
return this.fdur;
|
||||
}
|
||||
|
||||
public int[] getSync() {
|
||||
return this.sync;
|
||||
}
|
||||
|
||||
public static MPSStreamIndex parseIndex(ByteBuffer index) {
|
||||
int streamId = index.get() & 0xFF;
|
||||
int fCnt = index.getInt();
|
||||
int[] fsizes = new int[fCnt];
|
||||
for (int i = 0; i < fCnt; i++)
|
||||
fsizes[i] = index.getInt();
|
||||
int fptsCnt = index.getInt();
|
||||
int[] fpts = new int[fptsCnt];
|
||||
for (int j = 0; j < fptsCnt; j++)
|
||||
fpts[j] = index.getInt();
|
||||
int fdurCnt = index.getInt();
|
||||
int[] fdur = new int[fdurCnt];
|
||||
for (int k = 0; k < fdurCnt; k++)
|
||||
fdur[k] = index.getInt();
|
||||
int syncCount = index.getInt();
|
||||
int[] sync = new int[syncCount];
|
||||
for (int m = 0; m < syncCount; m++)
|
||||
sync[m] = index.getInt();
|
||||
return new MPSStreamIndex(streamId, fsizes, fpts, fdur, sync);
|
||||
}
|
||||
|
||||
public void serialize(ByteBuffer index) {
|
||||
index.put((byte)this.streamId);
|
||||
index.putInt(this.fsizes.length);
|
||||
for (int m = 0; m < this.fsizes.length; m++)
|
||||
index.putInt(this.fsizes[m]);
|
||||
index.putInt(this.fpts.length);
|
||||
for (int k = 0; k < this.fpts.length; k++)
|
||||
index.putInt(this.fpts[k]);
|
||||
index.putInt(this.fdur.length);
|
||||
for (int j = 0; j < this.fdur.length; j++)
|
||||
index.putInt(this.fdur[j]);
|
||||
index.putInt(this.sync.length);
|
||||
for (int i = 0; i < this.sync.length; i++)
|
||||
index.putInt(this.sync[i]);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
return (this.fpts.length << 2) + (this.fdur.length << 2) + (this.sync.length << 2) + (this.fsizes.length << 2) + 64;
|
||||
}
|
||||
}
|
||||
|
||||
public MPSIndex(long[] pesTokens, RunLength.Integer pesStreamIds, MPSStreamIndex[] streams) {
|
||||
this.pesTokens = pesTokens;
|
||||
this.pesStreamIds = pesStreamIds;
|
||||
this.streams = streams;
|
||||
}
|
||||
|
||||
public long[] getPesTokens() {
|
||||
return this.pesTokens;
|
||||
}
|
||||
|
||||
public RunLength.Integer getPesStreamIds() {
|
||||
return this.pesStreamIds;
|
||||
}
|
||||
|
||||
public MPSStreamIndex[] getStreams() {
|
||||
return this.streams;
|
||||
}
|
||||
|
||||
public static MPSIndex parseIndex(ByteBuffer index) {
|
||||
int pesCnt = index.getInt();
|
||||
long[] pesTokens = new long[pesCnt];
|
||||
for (int i = 0; i < pesCnt; i++)
|
||||
pesTokens[i] = index.getLong();
|
||||
RunLength.Integer pesStreamId = RunLength.Integer.parse(index);
|
||||
int nStreams = index.getInt();
|
||||
MPSStreamIndex[] streams = new MPSStreamIndex[nStreams];
|
||||
for (int j = 0; j < nStreams; j++)
|
||||
streams[j] = MPSStreamIndex.parseIndex(index);
|
||||
return new MPSIndex(pesTokens, pesStreamId, streams);
|
||||
}
|
||||
|
||||
public void serializeTo(ByteBuffer index) {
|
||||
index.putInt(this.pesTokens.length);
|
||||
for (int i = 0; i < this.pesTokens.length; i++)
|
||||
index.putLong(this.pesTokens[i]);
|
||||
this.pesStreamIds.serialize(index);
|
||||
index.putInt(this.streams.length);
|
||||
for (MPSStreamIndex mpsStreamIndex : this.streams)
|
||||
mpsStreamIndex.serialize(index);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
int size = (this.pesTokens.length << 3) + this.pesStreamIds.estimateSize();
|
||||
for (MPSStreamIndex mpsStreamIndex : this.streams)
|
||||
size += mpsStreamIndex.estimateSize();
|
||||
return size + 64;
|
||||
}
|
||||
|
||||
public static long makePESToken(long leading, long pesLen, long payloadLen) {
|
||||
return leading << 48L | pesLen << 24L | payloadLen;
|
||||
}
|
||||
|
||||
public static int leadingSize(long token) {
|
||||
return (int)(token >> 48L) & 0xFFFF;
|
||||
}
|
||||
|
||||
public static int pesLen(long token) {
|
||||
return (int)(token >> 24L) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
public static int payLoadSize(long token) {
|
||||
return (int)token & 0xFFFFFF;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package org.jcodec.containers.mps.index;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.containers.mps.MPSUtils;
|
||||
import org.jcodec.containers.mps.PESPacket;
|
||||
|
||||
public class MPSIndexer extends BaseIndexer {
|
||||
private long predFileStart;
|
||||
|
||||
public void index(File source, NIOUtils.FileReaderListener listener) throws IOException {
|
||||
newReader().readFile(source, 65536, listener);
|
||||
}
|
||||
|
||||
public void indexChannel(SeekableByteChannel source, NIOUtils.FileReaderListener listener) throws IOException {
|
||||
newReader().readChannel(source, 65536, listener);
|
||||
}
|
||||
|
||||
private NIOUtils.FileReader newReader() {
|
||||
final MPSIndexer self = this;
|
||||
return new NIOUtils.FileReader() {
|
||||
protected void data(ByteBuffer data, long filePos) {
|
||||
self.analyseBuffer(data, filePos);
|
||||
}
|
||||
|
||||
protected void done() {
|
||||
self.finishAnalyse();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected void pes(ByteBuffer pesBuffer, long start, int pesLen, int stream) {
|
||||
if (!MPSUtils.mediaStream(stream))
|
||||
return;
|
||||
PESPacket pesHeader = MPSUtils.readPESHeader(pesBuffer, start);
|
||||
int leading = 0;
|
||||
if (this.predFileStart != start)
|
||||
leading += (int)(start - this.predFileStart);
|
||||
this.predFileStart = start + (long)pesLen;
|
||||
savePESMeta(stream, MPSIndex.makePESToken((long)leading, (long)pesLen, (long)pesBuffer.remaining()));
|
||||
getAnalyser(stream).pkt(pesBuffer, pesHeader);
|
||||
}
|
||||
|
||||
public static void main1(String[] args) throws IOException {
|
||||
MPSIndexer indexer = new MPSIndexer();
|
||||
indexer.index(new File(args[0]), new NIOUtils.FileReaderListener() {
|
||||
public void progress(int percentDone) {
|
||||
System.out.println(percentDone);
|
||||
}
|
||||
});
|
||||
ByteBuffer index = ByteBuffer.allocate(65536);
|
||||
indexer.serialize().serializeTo(index);
|
||||
NIOUtils.writeTo(index, new File(args[1]));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package org.jcodec.containers.mps.index;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import org.jcodec.api.NotSupportedException;
|
||||
import org.jcodec.common.DemuxerTrackMeta;
|
||||
import org.jcodec.common.SeekableDemuxerTrack;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.model.Packet;
|
||||
import org.jcodec.containers.mps.MPSUtils;
|
||||
import org.jcodec.platform.Platform;
|
||||
|
||||
public class MPSRandomAccessDemuxer {
|
||||
private Stream[] streams;
|
||||
|
||||
private long[] pesTokens;
|
||||
|
||||
private int[] pesStreamIds;
|
||||
|
||||
public MPSRandomAccessDemuxer(SeekableByteChannel ch, MPSIndex mpsIndex) throws IOException {
|
||||
this.pesTokens = mpsIndex.getPesTokens();
|
||||
this.pesStreamIds = mpsIndex.getPesStreamIds().flattern();
|
||||
MPSIndex.MPSStreamIndex[] streamIndices = mpsIndex.getStreams();
|
||||
this.streams = new Stream[streamIndices.length];
|
||||
for (int i = 0; i < streamIndices.length; i++)
|
||||
this.streams[i] = newStream(ch, streamIndices[i]);
|
||||
}
|
||||
|
||||
protected Stream newStream(SeekableByteChannel ch, MPSIndex.MPSStreamIndex streamIndex) throws IOException {
|
||||
return new Stream(this, streamIndex, ch);
|
||||
}
|
||||
|
||||
public Stream[] getStreams() {
|
||||
return this.streams;
|
||||
}
|
||||
|
||||
public static class Stream extends MPSIndex.MPSStreamIndex implements SeekableDemuxerTrack {
|
||||
private static final int MPEG_TIMESCALE = 90000;
|
||||
|
||||
private int curPesIdx;
|
||||
|
||||
private int curFrame;
|
||||
|
||||
private ByteBuffer pesBuf;
|
||||
|
||||
private int _seekToFrame = -1;
|
||||
|
||||
protected SeekableByteChannel source;
|
||||
|
||||
private long[] foffs;
|
||||
|
||||
private MPSRandomAccessDemuxer demuxer;
|
||||
|
||||
public Stream(MPSRandomAccessDemuxer demuxer, MPSIndex.MPSStreamIndex streamIndex, SeekableByteChannel source) throws IOException {
|
||||
super(streamIndex.streamId, streamIndex.fsizes, streamIndex.fpts, streamIndex.fdur, streamIndex.sync);
|
||||
this.demuxer = demuxer;
|
||||
this.source = source;
|
||||
this.foffs = new long[this.fsizes.length];
|
||||
long curOff = 0L;
|
||||
for (int i = 0; i < this.fsizes.length; i++) {
|
||||
this.foffs[i] = curOff;
|
||||
curOff += (long)this.fsizes[i];
|
||||
}
|
||||
int[] seg = Platform.copyOfInt(streamIndex.getFpts(), 100);
|
||||
Arrays.sort(seg);
|
||||
this._seekToFrame = 0;
|
||||
seekToFrame();
|
||||
}
|
||||
|
||||
public Packet nextFrame() throws IOException {
|
||||
seekToFrame();
|
||||
if (this.curFrame >= this.fsizes.length)
|
||||
return null;
|
||||
int fs = this.fsizes[this.curFrame];
|
||||
ByteBuffer result = ByteBuffer.allocate(fs);
|
||||
return _nextFrame(result);
|
||||
}
|
||||
|
||||
private Packet _nextFrame(ByteBuffer buf) throws IOException {
|
||||
seekToFrame();
|
||||
if (this.curFrame >= this.fsizes.length)
|
||||
return null;
|
||||
int fs = this.fsizes[this.curFrame];
|
||||
ByteBuffer result = buf.duplicate();
|
||||
result.limit(result.position() + fs);
|
||||
while (result.hasRemaining()) {
|
||||
if (this.pesBuf.hasRemaining()) {
|
||||
result.put(NIOUtils.read(this.pesBuf, Math.min(this.pesBuf.remaining(), result.remaining())));
|
||||
continue;
|
||||
}
|
||||
this.curPesIdx++;
|
||||
long posShift = 0L;
|
||||
while (this.demuxer.pesStreamIds[this.curPesIdx] != this.streamId) {
|
||||
posShift += (long)(MPSIndex.pesLen(this.demuxer.pesTokens[this.curPesIdx]) + MPSIndex.leadingSize(this.demuxer.pesTokens[this.curPesIdx]));
|
||||
this.curPesIdx++;
|
||||
}
|
||||
skip(posShift + (long)MPSIndex.leadingSize(this.demuxer.pesTokens[this.curPesIdx]));
|
||||
int pesLen = MPSIndex.pesLen(this.demuxer.pesTokens[this.curPesIdx]);
|
||||
this.pesBuf = fetch(pesLen);
|
||||
MPSUtils.readPESHeader(this.pesBuf, 0L);
|
||||
}
|
||||
result.flip();
|
||||
Packet pkt = Packet.createPacket(result, (long)this.fpts[this.curFrame], 90000, (long)this.fdur[this.curFrame], (long)this.curFrame, (
|
||||
this.sync.length == 0 || Arrays.binarySearch(this.sync, this.curFrame) >= 0) ? Packet.FrameType.KEY : Packet.FrameType.INTER, null);
|
||||
this.curFrame++;
|
||||
return pkt;
|
||||
}
|
||||
|
||||
protected ByteBuffer fetch(int pesLen) throws IOException {
|
||||
return NIOUtils.fetchFromChannel(this.source, pesLen);
|
||||
}
|
||||
|
||||
protected void skip(long leadingSize) throws IOException {
|
||||
this.source.setPosition(this.source.position() + leadingSize);
|
||||
}
|
||||
|
||||
protected void reset() throws IOException {
|
||||
this.source.setPosition(0L);
|
||||
}
|
||||
|
||||
public DemuxerTrackMeta getMeta() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean gotoFrame(long frameNo) {
|
||||
this._seekToFrame = (int)frameNo;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean gotoSyncFrame(long frameNo) {
|
||||
for (int i = 0; i < this.sync.length; i++) {
|
||||
if ((long)this.sync[i] > frameNo) {
|
||||
this._seekToFrame = this.sync[i - 1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
this._seekToFrame = this.sync[this.sync.length - 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
private void seekToFrame() throws IOException {
|
||||
if (this._seekToFrame == -1)
|
||||
return;
|
||||
this.curFrame = this._seekToFrame;
|
||||
long payloadOff = this.foffs[this.curFrame];
|
||||
long posShift = 0L;
|
||||
reset();
|
||||
for (this.curPesIdx = 0;; this.curPesIdx++) {
|
||||
if (this.demuxer.pesStreamIds[this.curPesIdx] == this.streamId) {
|
||||
int payloadSize = MPSIndex.payLoadSize(this.demuxer.pesTokens[this.curPesIdx]);
|
||||
if (payloadOff < (long)payloadSize)
|
||||
break;
|
||||
payloadOff -= (long)payloadSize;
|
||||
}
|
||||
posShift += (long)(MPSIndex.pesLen(this.demuxer.pesTokens[this.curPesIdx]) + MPSIndex.leadingSize(this.demuxer.pesTokens[this.curPesIdx]));
|
||||
}
|
||||
skip(posShift + (long)MPSIndex.leadingSize(this.demuxer.pesTokens[this.curPesIdx]));
|
||||
this.pesBuf = fetch(MPSIndex.pesLen(this.demuxer.pesTokens[this.curPesIdx]));
|
||||
MPSUtils.readPESHeader(this.pesBuf, 0L);
|
||||
NIOUtils.skip(this.pesBuf, (int)payloadOff);
|
||||
this._seekToFrame = -1;
|
||||
}
|
||||
|
||||
public long getCurFrame() {
|
||||
return (long)this.curFrame;
|
||||
}
|
||||
|
||||
public void seek(double second) {
|
||||
throw new NotSupportedException("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package org.jcodec.containers.mps.index;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.RunLength;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
|
||||
public class MTSIndex {
|
||||
private MTSProgram[] programs;
|
||||
|
||||
public static MTSProgram createMTSProgram(MPSIndex mpsIndex, int target) {
|
||||
MTSProgram m = new MTSProgram(mpsIndex.pesTokens, mpsIndex.pesStreamIds, mpsIndex.streams, target);
|
||||
return m;
|
||||
}
|
||||
|
||||
public static class MTSProgram extends MPSIndex {
|
||||
private int targetGuid;
|
||||
|
||||
public MTSProgram(long[] pesTokens, RunLength.Integer pesStreamIds, MPSIndex.MPSStreamIndex[] streams, int targetGuid) {
|
||||
super(pesTokens, pesStreamIds, streams);
|
||||
this.targetGuid = targetGuid;
|
||||
}
|
||||
|
||||
public int getTargetGuid() {
|
||||
return this.targetGuid;
|
||||
}
|
||||
|
||||
public void serializeTo(ByteBuffer index) {
|
||||
index.putInt(this.targetGuid);
|
||||
super.serializeTo(index);
|
||||
}
|
||||
|
||||
public static MTSProgram parse(ByteBuffer read) {
|
||||
int targetGuid = read.getInt();
|
||||
return MTSIndex.createMTSProgram(MPSIndex.parseIndex(read), targetGuid);
|
||||
}
|
||||
}
|
||||
|
||||
public MTSIndex(MTSProgram[] programs) {
|
||||
this.programs = programs;
|
||||
}
|
||||
|
||||
public MTSProgram[] getPrograms() {
|
||||
return this.programs;
|
||||
}
|
||||
|
||||
public static MTSIndex parse(ByteBuffer buf) {
|
||||
int numPrograms = buf.getInt();
|
||||
MTSProgram[] programs = new MTSProgram[numPrograms];
|
||||
for (int i = 0; i < numPrograms; i++) {
|
||||
int programDataSize = buf.getInt();
|
||||
programs[i] = MTSProgram.parse(NIOUtils.read(buf, programDataSize));
|
||||
}
|
||||
return new MTSIndex(programs);
|
||||
}
|
||||
|
||||
public int estimateSize() {
|
||||
int totalSize = 64;
|
||||
for (MTSProgram mtsProgram : this.programs)
|
||||
totalSize += 4 + mtsProgram.estimateSize();
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
public void serializeTo(ByteBuffer buf) {
|
||||
buf.putInt(this.programs.length);
|
||||
for (MTSProgram mtsAnalyser : this.programs) {
|
||||
ByteBuffer dup = buf.duplicate();
|
||||
NIOUtils.skip(buf, 4);
|
||||
mtsAnalyser.serializeTo(buf);
|
||||
dup.putInt(buf.position() - dup.position() - 4);
|
||||
}
|
||||
}
|
||||
|
||||
public ByteBuffer serialize() {
|
||||
ByteBuffer bb = ByteBuffer.allocate(estimateSize());
|
||||
serializeTo(bb);
|
||||
bb.flip();
|
||||
return bb;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package org.jcodec.containers.mps.index;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.Preconditions;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
import org.jcodec.common.logging.Logger;
|
||||
import org.jcodec.containers.mps.MPSUtils;
|
||||
import org.jcodec.containers.mps.MTSUtils;
|
||||
import org.jcodec.containers.mps.PESPacket;
|
||||
|
||||
public class MTSIndexer {
|
||||
public static final int BUFFER_SIZE = 96256;
|
||||
|
||||
private MTSAnalyser[] indexers;
|
||||
|
||||
public void index(File source, NIOUtils.FileReaderListener listener) throws IOException {
|
||||
indexReader(listener, MTSUtils.getMediaPids(source)).readFile(source, 96256, listener);
|
||||
}
|
||||
|
||||
public void indexChannel(SeekableByteChannel source, NIOUtils.FileReaderListener listener) throws IOException {
|
||||
indexReader(listener, MTSUtils.getMediaPidsFromChannel(source)).readChannel(source, 96256, listener);
|
||||
}
|
||||
|
||||
public NIOUtils.FileReader indexReader(NIOUtils.FileReaderListener listener, int[] targetGuids) throws IOException {
|
||||
this.indexers = new MTSAnalyser[targetGuids.length];
|
||||
for (int i = 0; i < targetGuids.length; i++)
|
||||
this.indexers[i] = new MTSAnalyser(targetGuids[i]);
|
||||
return new MTSFileReader(this);
|
||||
}
|
||||
|
||||
public MTSIndex serialize() {
|
||||
MTSIndex.MTSProgram[] programs = new MTSIndex.MTSProgram[this.indexers.length];
|
||||
for (int i = 0; i < this.indexers.length; i++)
|
||||
programs[i] = this.indexers[i].serializeTo();
|
||||
return new MTSIndex(programs);
|
||||
}
|
||||
|
||||
private static final class MTSFileReader extends NIOUtils.FileReader {
|
||||
private MTSIndexer indexer;
|
||||
|
||||
public MTSFileReader(MTSIndexer indexer) {
|
||||
this.indexer = indexer;
|
||||
}
|
||||
|
||||
protected void data(ByteBuffer data, long filePos) {
|
||||
analyseBuffer(data, filePos);
|
||||
}
|
||||
|
||||
protected void analyseBuffer(ByteBuffer buf, long pos) {
|
||||
while (buf.hasRemaining()) {
|
||||
ByteBuffer tsBuf = NIOUtils.read(buf, 188);
|
||||
pos += 188L;
|
||||
Preconditions.checkState((71 == (tsBuf.get() & 0xFF)));
|
||||
int guidFlags = (tsBuf.get() & 0xFF) << 8 | tsBuf.get() & 0xFF;
|
||||
int guid = guidFlags & 0x1FFF;
|
||||
for (int i = 0; i < this.indexer.indexers.length; i++) {
|
||||
if (guid == (this.indexer.indexers[i]).targetGuid) {
|
||||
int payloadStart = guidFlags >> 14 & 0x1;
|
||||
int b0 = tsBuf.get() & 0xFF;
|
||||
int counter = b0 & 0xF;
|
||||
if ((b0 & 0x20) != 0)
|
||||
NIOUtils.skip(tsBuf, tsBuf.get() & 0xFF);
|
||||
this.indexer.indexers[i].analyseBuffer(tsBuf, pos - (long)tsBuf.remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void done() {
|
||||
for (MTSIndexer.MTSAnalyser mtsAnalyser : this.indexer.indexers)
|
||||
mtsAnalyser.finishAnalyse();
|
||||
}
|
||||
}
|
||||
|
||||
private static class MTSAnalyser extends BaseIndexer {
|
||||
private int targetGuid;
|
||||
|
||||
private long predFileStartInTsPkt;
|
||||
|
||||
public MTSAnalyser(int targetGuid) {
|
||||
this.targetGuid = targetGuid;
|
||||
}
|
||||
|
||||
public MTSIndex.MTSProgram serializeTo() {
|
||||
return MTSIndex.createMTSProgram(serialize(), this.targetGuid);
|
||||
}
|
||||
|
||||
protected void pes(ByteBuffer pesBuffer, long start, int pesLen, int stream) {
|
||||
if (!MPSUtils.mediaStream(stream))
|
||||
return;
|
||||
Logger.debug(String.format("PES: %08x, %d", start, pesLen));
|
||||
PESPacket pesHeader = MPSUtils.readPESHeader(pesBuffer, start);
|
||||
int leadingTsPkt = 0;
|
||||
if (this.predFileStartInTsPkt != start)
|
||||
leadingTsPkt = (int)(start / 188L - this.predFileStartInTsPkt);
|
||||
this.predFileStartInTsPkt = (start + (long)pesLen) / 188L;
|
||||
int tsPktInPes = (int)(this.predFileStartInTsPkt - start / 188L);
|
||||
savePESMeta(stream, MPSIndex.makePESToken((long)leadingTsPkt, (long)tsPktInPes, (long)pesBuffer.remaining()));
|
||||
getAnalyser(stream).pkt(pesBuffer, pesHeader);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main1(String[] args) throws IOException {
|
||||
File src = new File(args[0]);
|
||||
MTSIndexer indexer = new MTSIndexer();
|
||||
indexer.index(src, new NIOUtils.FileReaderListener() {
|
||||
public void progress(int percentDone) {
|
||||
System.out.println(percentDone);
|
||||
}
|
||||
});
|
||||
MTSIndex index = indexer.serialize();
|
||||
NIOUtils.writeTo(index.serialize(), new File(args[1]));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
package org.jcodec.containers.mps.index;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.Preconditions;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.io.SeekableByteChannel;
|
||||
|
||||
public class MTSRandomAccessDemuxer {
|
||||
private MTSIndex.MTSProgram[] programs;
|
||||
|
||||
private SeekableByteChannel ch;
|
||||
|
||||
public MTSRandomAccessDemuxer(SeekableByteChannel ch, MTSIndex index) {
|
||||
this.programs = index.getPrograms();
|
||||
this.ch = ch;
|
||||
}
|
||||
|
||||
public int[] getGuids() {
|
||||
int[] guids = new int[this.programs.length];
|
||||
for (int i = 0; i < this.programs.length; i++)
|
||||
guids[i] = this.programs[i].getTargetGuid();
|
||||
return guids;
|
||||
}
|
||||
|
||||
public MPSRandomAccessDemuxer getProgramDemuxer(final int tgtGuid) throws IOException {
|
||||
MPSIndex index = getProgram(tgtGuid);
|
||||
return new MPSRandomAccessDemuxer(this.ch, index) {
|
||||
protected MPSRandomAccessDemuxer.Stream newStream(SeekableByteChannel ch, MPSIndex.MPSStreamIndex streamIndex) throws IOException {
|
||||
return new MPSRandomAccessDemuxer.Stream(this, streamIndex, ch) {
|
||||
protected ByteBuffer fetch(int pesLen) throws IOException {
|
||||
ByteBuffer bb = ByteBuffer.allocate(pesLen * 188);
|
||||
for (int i = 0; i < pesLen; i++) {
|
||||
ByteBuffer tsBuf = NIOUtils.fetchFromChannel(this.source, 188);
|
||||
Preconditions.checkState((71 == (tsBuf.get() & 0xFF)));
|
||||
int guidFlags = (tsBuf.get() & 0xFF) << 8 | tsBuf.get() & 0xFF;
|
||||
int guid = guidFlags & 0x1FFF;
|
||||
if (guid == tgtGuid) {
|
||||
int payloadStart = guidFlags >> 14 & 0x1;
|
||||
int b0 = tsBuf.get() & 0xFF;
|
||||
int counter = b0 & 0xF;
|
||||
if ((b0 & 0x20) != 0)
|
||||
NIOUtils.skip(tsBuf, tsBuf.get() & 0xFF);
|
||||
bb.put(tsBuf);
|
||||
}
|
||||
}
|
||||
bb.flip();
|
||||
return bb;
|
||||
}
|
||||
|
||||
protected void skip(long leadingSize) throws IOException {
|
||||
this.source.setPosition(this.source.position() + leadingSize * 188L);
|
||||
}
|
||||
|
||||
protected void reset() throws IOException {
|
||||
this.source.setPosition(0L);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private MPSIndex getProgram(int guid) {
|
||||
for (MTSIndex.MTSProgram mtsProgram : this.programs) {
|
||||
if (mtsProgram.getTargetGuid() == guid)
|
||||
return mtsProgram;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package org.jcodec.containers.mps.index;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import org.jcodec.codecs.mpeg12.MPEGDecoder;
|
||||
import org.jcodec.common.Codec;
|
||||
import org.jcodec.common.MuxerTrack;
|
||||
import org.jcodec.common.VideoCodecMeta;
|
||||
import org.jcodec.common.io.FileChannelWrapper;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.common.model.Packet;
|
||||
import org.jcodec.containers.mp4.Brand;
|
||||
import org.jcodec.containers.mp4.MP4Packet;
|
||||
import org.jcodec.containers.mp4.muxer.MP4Muxer;
|
||||
|
||||
public class MTSRandomAccessDemuxerMain {
|
||||
public static void main1(String[] args) throws IOException {
|
||||
MTSIndex index;
|
||||
MTSIndexer indexer = new MTSIndexer();
|
||||
File source = new File(args[0]);
|
||||
File indexFile = new File(source.getParentFile(), source.getName() + ".idx");
|
||||
if (!indexFile.exists()) {
|
||||
indexer.index(source, null);
|
||||
index = indexer.serialize();
|
||||
NIOUtils.writeTo(index.serialize(), indexFile);
|
||||
} else {
|
||||
System.out.println("Reading index from: " + indexFile.getName());
|
||||
index = MTSIndex.parse(NIOUtils.fetchFromFile(indexFile));
|
||||
}
|
||||
MTSRandomAccessDemuxer demuxer = new MTSRandomAccessDemuxer(NIOUtils.readableChannel(source), index);
|
||||
int[] guids = demuxer.getGuids();
|
||||
MPSRandomAccessDemuxer.Stream video = getVideoStream(demuxer.getProgramDemuxer(guids[0]));
|
||||
FileChannelWrapper ch = NIOUtils.writableChannel(new File(args[1]));
|
||||
MP4Muxer mp4Muxer = MP4Muxer.createMP4Muxer(ch, Brand.MOV);
|
||||
video.gotoSyncFrame(175L);
|
||||
Packet pkt = video.nextFrame();
|
||||
VideoCodecMeta meta = new MPEGDecoder().getCodecMeta(pkt.getData());
|
||||
MuxerTrack videoTrack = mp4Muxer.addVideoTrack(Codec.MPEG2, meta);
|
||||
long firstPts = pkt.getPts();
|
||||
for (int i = 0; pkt != null && i < 150; i++) {
|
||||
videoTrack.addFrame(MP4Packet.createMP4Packet(pkt.getData(), pkt.getPts() - firstPts, pkt.getTimescale(),
|
||||
pkt.getDuration(), pkt.getFrameNo(), pkt.getFrameType(), pkt.getTapeTimecode(), 0, pkt.getPts() - firstPts, 0));
|
||||
pkt = video.nextFrame();
|
||||
}
|
||||
mp4Muxer.finish();
|
||||
NIOUtils.closeQuietly(ch);
|
||||
}
|
||||
|
||||
private static MPSRandomAccessDemuxer.Stream getVideoStream(MPSRandomAccessDemuxer demuxer) {
|
||||
MPSRandomAccessDemuxer.Stream[] streams = demuxer.getStreams();
|
||||
for (MPSRandomAccessDemuxer.Stream stream : streams) {
|
||||
if (stream.getStreamId() >= 224 && stream.getStreamId() <= 239)
|
||||
return stream;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package org.jcodec.containers.mps.psi;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import org.jcodec.common.IntArrayList;
|
||||
import org.jcodec.common.IntIntMap;
|
||||
|
||||
public class PATSection extends PSISection {
|
||||
private int[] networkPids;
|
||||
|
||||
private IntIntMap programs;
|
||||
|
||||
public PATSection(PSISection psi, int[] networkPids, IntIntMap programs) {
|
||||
super(psi.tableId, psi.specificId, psi.versionNumber, psi.currentNextIndicator, psi.sectionNumber, psi.lastSectionNumber);
|
||||
this.networkPids = networkPids;
|
||||
this.programs = programs;
|
||||
}
|
||||
|
||||
public int[] getNetworkPids() {
|
||||
return this.networkPids;
|
||||
}
|
||||
|
||||
public IntIntMap getPrograms() {
|
||||
return this.programs;
|
||||
}
|
||||
|
||||
public static PATSection parsePAT(ByteBuffer data) {
|
||||
PSISection psi = PSISection.parsePSI(data);
|
||||
IntArrayList networkPids = IntArrayList.createIntArrayList();
|
||||
IntIntMap programs = new IntIntMap();
|
||||
while (data.remaining() > 4) {
|
||||
int programNum = data.getShort() & 0xFFFF;
|
||||
int w = data.getShort();
|
||||
int pid = w & 0x1FFF;
|
||||
if (programNum == 0) {
|
||||
networkPids.add(pid);
|
||||
continue;
|
||||
}
|
||||
programs.put(programNum, pid);
|
||||
}
|
||||
return new PATSection(psi, networkPids.toArray(), programs);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
package org.jcodec.containers.mps.psi;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.jcodec.common.io.NIOUtils;
|
||||
import org.jcodec.containers.mps.MPSUtils;
|
||||
import org.jcodec.containers.mps.MTSStreamType;
|
||||
|
||||
public class PMTSection extends PSISection {
|
||||
private int pcrPid;
|
||||
|
||||
private Tag[] tags;
|
||||
|
||||
private PMTStream[] streams;
|
||||
|
||||
public PMTSection(PSISection psi, int pcrPid, Tag[] tags, PMTStream[] streams) {
|
||||
super(psi.tableId, psi.specificId, psi.versionNumber, psi.currentNextIndicator, psi.sectionNumber, psi.lastSectionNumber);
|
||||
this.pcrPid = pcrPid;
|
||||
this.tags = tags;
|
||||
this.streams = streams;
|
||||
}
|
||||
|
||||
public int getPcrPid() {
|
||||
return this.pcrPid;
|
||||
}
|
||||
|
||||
public Tag[] getTags() {
|
||||
return this.tags;
|
||||
}
|
||||
|
||||
public PMTStream[] getStreams() {
|
||||
return this.streams;
|
||||
}
|
||||
|
||||
public static PMTSection parsePMT(ByteBuffer data) {
|
||||
PSISection psi = PSISection.parsePSI(data);
|
||||
int w1 = data.getShort() & 0xFFFF;
|
||||
int pcrPid = w1 & 0x1FFF;
|
||||
int w2 = data.getShort() & 0xFFFF;
|
||||
int programInfoLength = w2 & 0xFFF;
|
||||
List<Tag> tags = parseTags(NIOUtils.read(data, programInfoLength));
|
||||
List<PMTStream> streams = new ArrayList<>();
|
||||
while (data.remaining() > 4) {
|
||||
int streamType = data.get() & 0xFF;
|
||||
int wn = data.getShort() & 0xFFFF;
|
||||
int elementaryPid = wn & 0x1FFF;
|
||||
int wn1 = data.getShort() & 0xFFFF;
|
||||
int esInfoLength = wn1 & 0xFFF;
|
||||
ByteBuffer read = NIOUtils.read(data, esInfoLength);
|
||||
streams.add(new PMTStream(streamType, elementaryPid, MPSUtils.parseDescriptors(read)));
|
||||
}
|
||||
return new PMTSection(psi, pcrPid, tags.<Tag>toArray(new Tag[0]), streams.<PMTStream>toArray(new PMTStream[0]));
|
||||
}
|
||||
|
||||
static List<Tag> parseTags(ByteBuffer bb) {
|
||||
List<Tag> tags = new ArrayList<>();
|
||||
while (bb.hasRemaining()) {
|
||||
int tag = bb.get();
|
||||
int tagLen = bb.get();
|
||||
tags.add(new Tag(tag, NIOUtils.read(bb, tagLen)));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
public static class Tag {
|
||||
private int tag;
|
||||
|
||||
private ByteBuffer content;
|
||||
|
||||
public Tag(int tag, ByteBuffer content) {
|
||||
this.tag = tag;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public int getTag() {
|
||||
return this.tag;
|
||||
}
|
||||
|
||||
public ByteBuffer getContent() {
|
||||
return this.content;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PMTStream {
|
||||
private int streamTypeTag;
|
||||
|
||||
private int pid;
|
||||
|
||||
private List<MPSUtils.MPEGMediaDescriptor> descriptors;
|
||||
|
||||
private MTSStreamType streamType;
|
||||
|
||||
public PMTStream(int streamTypeTag, int pid, List<MPSUtils.MPEGMediaDescriptor> descriptors) {
|
||||
this.streamTypeTag = streamTypeTag;
|
||||
this.pid = pid;
|
||||
this.descriptors = descriptors;
|
||||
this.streamType = MTSStreamType.fromTag(streamTypeTag);
|
||||
}
|
||||
|
||||
public int getStreamTypeTag() {
|
||||
return this.streamTypeTag;
|
||||
}
|
||||
|
||||
public MTSStreamType getStreamType() {
|
||||
return this.streamType;
|
||||
}
|
||||
|
||||
public int getPid() {
|
||||
return this.pid;
|
||||
}
|
||||
|
||||
public List<MPSUtils.MPEGMediaDescriptor> getDesctiptors() {
|
||||
return this.descriptors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package org.jcodec.containers.mps.psi;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PSISection {
|
||||
protected int tableId;
|
||||
|
||||
protected int specificId;
|
||||
|
||||
protected int versionNumber;
|
||||
|
||||
protected int currentNextIndicator;
|
||||
|
||||
protected int sectionNumber;
|
||||
|
||||
protected int lastSectionNumber;
|
||||
|
||||
public PSISection(int tableId, int specificId, int versionNumber, int currentNextIndicator, int sectionNumber, int lastSectionNumber) {
|
||||
this.tableId = tableId;
|
||||
this.specificId = specificId;
|
||||
this.versionNumber = versionNumber;
|
||||
this.currentNextIndicator = currentNextIndicator;
|
||||
this.sectionNumber = sectionNumber;
|
||||
this.lastSectionNumber = lastSectionNumber;
|
||||
}
|
||||
|
||||
public static PSISection parsePSI(ByteBuffer data) {
|
||||
int tableId = data.get() & 0xFF;
|
||||
int w0 = data.getShort() & 0xFFFF;
|
||||
if ((w0 & 0xC000) != 32768)
|
||||
throw new RuntimeException("Invalid section data");
|
||||
int sectionLength = w0 & 0xFFF;
|
||||
data.limit(data.position() + sectionLength);
|
||||
int specificId = data.getShort() & 0xFFFF;
|
||||
int b0 = data.get() & 0xFF;
|
||||
int versionNumber = b0 >> 1 & 0x1F;
|
||||
int currentNextIndicator = b0 & 0x1;
|
||||
int sectionNumber = data.get() & 0xFF;
|
||||
int lastSectionNumber = data.get() & 0xFF;
|
||||
return new PSISection(tableId, specificId, versionNumber, currentNextIndicator, sectionNumber, lastSectionNumber);
|
||||
}
|
||||
|
||||
public int getTableId() {
|
||||
return this.tableId;
|
||||
}
|
||||
|
||||
public int getSpecificId() {
|
||||
return this.specificId;
|
||||
}
|
||||
|
||||
public int getVersionNumber() {
|
||||
return this.versionNumber;
|
||||
}
|
||||
|
||||
public int getCurrentNextIndicator() {
|
||||
return this.currentNextIndicator;
|
||||
}
|
||||
|
||||
public int getSectionNumber() {
|
||||
return this.sectionNumber;
|
||||
}
|
||||
|
||||
public int getLastSectionNumber() {
|
||||
return this.lastSectionNumber;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue