package org.jcodec.containers.mps; import static java.util.Arrays.asList; import static org.jcodec.containers.mps.MPSUtils.readPESHeader; 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.common.tools.MainUtils.Cmd; import org.jcodec.platform.Platform; import java.io.File; import java.io.IOException; import java.lang.IllegalAccessException; import java.lang.IllegalArgumentException; import java.lang.StringBuilder; import java.lang.System; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.HashMap; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Dumps MPEG Proram stream file. Can firther parse MPEG elementary stream * packets. * * @author The JCodec project * */ public class MPSDump { private static final String DUMP_FROM = "dump-from"; private static final String STOP_AT = "stop-at"; protected ReadableByteChannel ch; public MPSDump(ReadableByteChannel ch) { this.ch = ch; } public static void main1(String[] args) throws IOException { FileChannelWrapper ch = null; try { Cmd cmd = MainUtils.parseArguments(args); if (cmd.args.length < 1) { HashMap<String, String> map = new HashMap<String, String>(); map.put(STOP_AT, "Stop reading at timestamp"); map.put(DUMP_FROM, "Start dumping from timestamp"); MainUtils.printHelp(map, 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(0x100000); PESPacket pkt = null; int hdrSize = 0; for (long position = 0;;) { position -= buffer.position(); if(fillBuffer(buffer) == -1) break; buffer.flip(); if (buffer.remaining() < 4) break; position += 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 >= 0xe0 && pkt.streamId <= 0xef) { analyzer.analyzeMpegVideoPacket(payload); } if (buffer.remaining() < 32) { pkt = null; break; } skipToNextPES(buffer); if (buffer.remaining() < 32) { pkt = null; break; } hdrSize = buffer.position(); pkt = readPESHeader(buffer, position - 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 ch.read(buffer); } protected void logPes(PESPacket pkt, int hdrSize, ByteBuffer payload) { System.out.println(pkt.streamId + "(" + (pkt.streamId >= 0xe0 ? "video" : "audio") + ")" + " [" + pkt.pos + ", " + (payload.remaining() + hdrSize) + "], pts: " + pkt.pts + ", dts: " + pkt.dts); } 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 >= 0x1bd && marker <= 0x1ff && marker != 0x1be) 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 >= 0x1b9) { result.limit(copy.position()); buffer.position(copy.position()); return result; } copy.getInt(); MPEGUtil.gotoNextMarker(copy); } return null; } private static class MPEGVideoAnalyzer { private int nextStartCode = 0xffffffff; 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(0x100000); } private void analyzeMpegVideoPacket(ByteBuffer buffer) { int pos = buffer.position(); int bufSize = buffer.remaining(); while (buffer.hasRemaining()) { bselPayload.put((byte) (nextStartCode >> 24)); nextStartCode = (nextStartCode << 8) | (buffer.get() & 0xff); if (nextStartCode >= 0x100 && nextStartCode <= 0x1b8) { bselPayload.flip(); bselPayload.getInt(); if (bselStartCode != 0) { if (bselBufInd != curBufInd) bselOffset -= prevBufSize; dumpBSEl(bselStartCode, bselOffset, bselPayload); } bselPayload.clear(); bselStartCode = nextStartCode; bselOffset = buffer.position() - 4 - pos; bselBufInd = curBufInd; } } ++curBufInd; prevBufSize = bufSize; } private void dumpBSEl(int mark, int offset, ByteBuffer b) { System.out.print(String.format("marker: 0x%02x [@%d] ( ", mark, offset)); if (mark == 0x100) dumpPictureHeader(b); else if (mark <= 0x1af) System.out.print(MainUtils.colorBright(String.format("slice @0x%02x", mark - 0x101), MainUtils.ANSIColor.BLACK, true)); else if (mark == 0x1b3) dumpSequenceHeader(b); else if (mark == 0x1b5) dumpExtension(b); else if (mark == 0x1b8) 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 (picHeader == null) { if (sequenceHeader != null) { switch (extType) { case SequenceExtension.Sequence_Extension: sequenceExtension = SequenceExtension.read(_in); dumpSequenceExtension(sequenceExtension); break; case SequenceScalableExtension.Sequence_Scalable_Extension: dumpSequenceScalableExtension(SequenceScalableExtension.read(_in)); break; case SequenceDisplayExtension.Sequence_Display_Extension: dumpSequenceDisplayExtension(SequenceDisplayExtension.read(_in)); break; default: System.out.print(MainUtils.colorBright("extension " + extType, MainUtils.ANSIColor.GREEN, true)); } } else { System.out.print(MainUtils.colorBright("dangling extension " + extType, MainUtils.ANSIColor.GREEN, true)); } } else { switch (extType) { case QuantMatrixExtension.Quant_Matrix_Extension: dumpQuantMatrixExtension(QuantMatrixExtension.read(_in)); break; case CopyrightExtension.Copyright_Extension: dumpCopyrightExtension(CopyrightExtension.read(_in)); break; case PictureDisplayExtension.Picture_Display_Extension: if (sequenceHeader != null && pictureCodingExtension != null) dumpPictureDisplayExtension(PictureDisplayExtension.read(_in, sequenceExtension, pictureCodingExtension)); break; case PictureCodingExtension.Picture_Coding_Extension: pictureCodingExtension = PictureCodingExtension.read(_in); dumpPictureCodingExtension(pictureCodingExtension); break; case PictureSpatialScalableExtension.Picture_Spatial_Scalable_Extension: dumpPictureSpatialScalableExtension(PictureSpatialScalableExtension.read(_in)); break; case PictureTemporalScalableExtension.Picture_Temporal_Scalable_Extension: dumpPictureTemporalScalableExtension(PictureTemporalScalableExtension.read(_in)); break; default: System.out.print(MainUtils.colorBright("extension " + extType, MainUtils.ANSIColor.GREEN, true)); } } } 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())) continue; 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) { picHeader = null; pictureCodingExtension = null; sequenceExtension = null; sequenceHeader = SequenceHeader.read(b); System.out.print(MainUtils.colorBright("sequence header", MainUtils.ANSIColor.BLUE, true)); } private void dumpPictureHeader(ByteBuffer b) { picHeader = PictureHeader.read(b); pictureCodingExtension = null; System.out.print(MainUtils.colorBright("picture header" + " <type:" + (picHeader.picture_coding_type == 1 ? "I" : (picHeader.picture_coding_type == 2 ? "P" : "B")) + ", temp_ref:" + picHeader.temporal_reference + ">", MainUtils.ANSIColor.BROWN, true)); } } }