package org.taktik.mpegts; import java.nio.ByteBuffer; import com.google.common.base.Preconditions; import org.taktik.ioutils.NIOUtils; public class MTSPacket extends PacketSupport { private boolean transportErrorIndicator; // Transport Error Indicator (TEI) private boolean payloadUnitStartIndicator; // Payload Unit Start Indicator private boolean transportPriority; // Transport Priority private int pid; // Packet Identifier (PID) private int scramblingControl; // Scrambling control private boolean adaptationFieldExist; // Adaptation field exist private boolean containsPayload; // Contains payload private int continuityCounter; // Continuity counter private AdaptationField adaptationField; private ByteBuffer payload; public static class AdaptationField { private MTSPacket packet; private boolean discontinuityIndicator; // Discontinuity indicator private boolean randomAccessIndicator; // Random Access indicator private boolean elementaryStreamPriorityIndicator; // Elementary stream priority indicator private boolean pcrFlag; // PCR flag private boolean opcrFlag; // OPCR flag private boolean splicingPointFlag; // Splicing point flag private boolean transportPrivateDataFlag; // Transport private data flag private boolean adaptationFieldExtensionFlag; // Adaptation field extension flag private PCR pcr; // PCR private PCR opcr; // OPCR private byte spliceCountdown; // Splice countdown private byte[] privateData; // Private data private byte[] extension; // Extension public static class PCR { private AdaptationField field; public long base; // 33 bits public int extension; // 9 bits public byte reserved; // 6 bits public PCR(AdaptationField field, long base, int extension, byte reserved) { this.base = base; this.extension = extension; this.reserved = reserved; this.field = field; } public long getValue() { return base * 300 + extension; } public void setValue(long value) { base = value / 300; extension = (int)value % 300; field.markDirty(); } public void write(ByteBuffer buffer) { buffer.putInt((int) ((base & 0x1FFFFFFFFL) >> 1)); int middleByte = 0; middleByte |= ((base & 0x1) << 7); middleByte |= ((reserved & 0x3F) << 1); middleByte |= ((extension & 0x1FF) >> 8); buffer.put((byte) middleByte); buffer.put((byte) (extension & 0xff)); } } public AdaptationField(MTSPacket packet, boolean discontinuityIndicator, boolean randomAccessIndicator, boolean elementaryStreamPriorityIndicator, boolean pcrFlag, boolean opcrFlag, boolean splicingPointFlag, boolean transportPrivateDataFlag, boolean adaptationFieldExtensionFlag, PCR pcr, PCR opcr, byte spliceCountdown, byte[] privateData, byte[] extension) { this.packet = packet; this.discontinuityIndicator = discontinuityIndicator; this.randomAccessIndicator = randomAccessIndicator; this.elementaryStreamPriorityIndicator = elementaryStreamPriorityIndicator; this.pcrFlag = pcrFlag; this.opcrFlag = opcrFlag; this.splicingPointFlag = splicingPointFlag; this.transportPrivateDataFlag = transportPrivateDataFlag; this.adaptationFieldExtensionFlag = adaptationFieldExtensionFlag; this.pcr = pcr; if (this.pcr != null) { this.pcr.field = this; } this.opcr = opcr; if (this.opcr != null) { this.opcr.field = this; } this.spliceCountdown = spliceCountdown; this.privateData = privateData; this.extension = extension; } public boolean isDiscontinuityIndicator() { return discontinuityIndicator; } public void setDiscontinuityIndicator(boolean discontinuityIndicator) { this.discontinuityIndicator = discontinuityIndicator; markDirty(); } private void markDirty() { packet.markDirty(); } public boolean isRandomAccessIndicator() { return randomAccessIndicator; } public void setRandomAccessIndicator(boolean randomAccessIndicator) { this.randomAccessIndicator = randomAccessIndicator; markDirty(); } public boolean isElementaryStreamPriorityIndicator() { return elementaryStreamPriorityIndicator; } public void setElementaryStreamPriorityIndicator(boolean elementaryStreamPriorityIndicator) { this.elementaryStreamPriorityIndicator = elementaryStreamPriorityIndicator; markDirty(); } public boolean isPcrFlag() { return pcrFlag; } public void setPcrFlag(boolean pcrFlag) { this.pcrFlag = pcrFlag; markDirty(); } public boolean isOpcrFlag() { return opcrFlag; } public void setOpcrFlag(boolean opcrFlag) { this.opcrFlag = opcrFlag; markDirty(); } public boolean isSplicingPointFlag() { return splicingPointFlag; } public void setSplicingPointFlag(boolean splicingPointFlag) { this.splicingPointFlag = splicingPointFlag; markDirty(); } public boolean isTransportPrivateDataFlag() { return transportPrivateDataFlag; } public void setTransportPrivateDataFlag(boolean transportPrivateDataFlag) { this.transportPrivateDataFlag = transportPrivateDataFlag; markDirty(); } public boolean isAdaptationFieldExtensionFlag() { return adaptationFieldExtensionFlag; } public void setAdaptationFieldExtensionFlag(boolean adaptationFieldExtensionFlag) { this.adaptationFieldExtensionFlag = adaptationFieldExtensionFlag; markDirty(); } public PCR getPcr() { return pcr; } public void setPcr(PCR pcr) { this.pcr = pcr; markDirty(); } public PCR getOpcr() { return opcr; } public void setOpcr(PCR opcr) { this.opcr = opcr; markDirty(); } public byte getSpliceCountdown() { return spliceCountdown; } public void setSpliceCountdown(byte spliceCountdown) { this.spliceCountdown = spliceCountdown; markDirty(); } public byte[] getPrivateData() { return privateData; } public void setPrivateData(byte[] privateData) { this.privateData = privateData; markDirty(); } public byte[] getExtension() { return extension; } public void setExtension(byte[] extension) { this.extension = extension; markDirty(); } public void write(ByteBuffer buffer, int payloadLength) { int length = 183 - payloadLength; int remaining = length; buffer.put((byte) (length & 0xff)); int firstByte = 0; // Discontinuity indicator if (discontinuityIndicator) { firstByte |= 0x80; } // Random Access indicator if (randomAccessIndicator) { firstByte |= 0x40; } // Elementary stream priority indicator if (elementaryStreamPriorityIndicator) { firstByte |= 0x20; } // PCR flag if (pcrFlag) { firstByte |= 0x10; } // OPCR flag if (opcrFlag) { firstByte |= 0x08; } // Splicing point flag if (splicingPointFlag) { firstByte |= 0x04; } // Transport private data flag if (transportPrivateDataFlag) { firstByte |= 0x2; } // Adaptation field extension flag if (adaptationFieldExtensionFlag) { firstByte |= 0x01; } buffer.put((byte) firstByte); remaining --; if (pcrFlag && pcr != null) { pcr.write(buffer); remaining -= 6; } if (opcrFlag && opcr != null) { opcr.write(buffer); remaining -= 6; } if (splicingPointFlag) { buffer.put(spliceCountdown); remaining--; } if (transportPrivateDataFlag && privateData != null) { buffer.put(privateData); remaining -= privateData.length; } if (adaptationFieldExtensionFlag && extension != null) { buffer.put(extension); remaining -= extension.length; } if (remaining < 0) { throw new IllegalStateException("Adaptation field too big!"); } while (remaining-- > 0) { buffer.put((byte)0); } } } public MTSPacket(boolean transportErrorIndicator, boolean payloadUnitStartIndicator, boolean transportPriority, int pid, int scramblingControl, int continuityCounter) { super(); this.buffer = ByteBuffer.allocate(Constants.MPEGTS_PACKET_SIZE); this.transportErrorIndicator = transportErrorIndicator; this.payloadUnitStartIndicator = payloadUnitStartIndicator; this.transportPriority = transportPriority; this.pid = pid; this.scramblingControl = scramblingControl; this.continuityCounter = continuityCounter; this.adaptationFieldExist = false; this.containsPayload = false; } public MTSPacket(ByteBuffer buffer) { super(buffer); } @Override protected void write() { // First write payload int payloadLength = 0; if (containsPayload && payload != null) { payload.clear(); payloadLength = payload.capacity(); buffer.position(Constants.MPEGTS_PACKET_SIZE - payloadLength); buffer.put(payload); } buffer.rewind(); // First byte buffer.put((byte) 0x47); // Bytes 2->3 int secondAndThirdBytes = 0; if (transportErrorIndicator) { secondAndThirdBytes |= 0x8000; } if (payloadUnitStartIndicator) { secondAndThirdBytes |= 0x4000; } if (transportPriority) { secondAndThirdBytes |= 0x2000; } secondAndThirdBytes |= (pid & 0x1fff); buffer.putShort((short) secondAndThirdBytes); int fourthByte = 0; // Byte 4 fourthByte |= (scramblingControl & 0xc0); if (adaptationFieldExist) { fourthByte |= 0x20; } if (containsPayload) { fourthByte |= 0x10; } fourthByte |= (continuityCounter & 0x0f); buffer.put((byte) fourthByte); // Adaptation field if (adaptationFieldExist) { if (adaptationField == null) { buffer.put((byte) 0); } else { adaptationField.write(buffer, payloadLength); } } // Payload if (containsPayload && payload != null) { payload.rewind(); buffer.put(payload); } if (buffer.remaining() != 0) { throw new IllegalStateException("buffer.remaining() = " + buffer.remaining() + ", should be zero!"); } } protected void parse() { // Sync byte int marker = buffer.get() & 0xff; Preconditions.checkArgument(Constants.TS_MARKER == marker); // Second/Third byte int secondAndThirdBytes = buffer.getShort(); // Transport Error Indicator (TEI) boolean transportErrorIndicator = (secondAndThirdBytes & 0x8000) != 0; // Payload Unit Start Indicator boolean payloadUnitStartIndicator = (secondAndThirdBytes & 0x4000) != 0; // Transport Priority boolean transportPriority = (secondAndThirdBytes & 0x2000) != 0; // Packet Identifier (PID) int pid = secondAndThirdBytes & 0x1fff; // Fourth byte int fourthByte = buffer.get() & 0xff; // Scrambling control int scramblingControl = fourthByte & 0xc0; // Adaptation field exist boolean adaptationFieldExist = (fourthByte & 0x20) != 0; // Contains payload boolean containsPayload = (fourthByte & 0x10) != 0; // Continuity counter int continuityCounter = fourthByte & 0x0f; MTSPacket.AdaptationField adaptationField = null; if (adaptationFieldExist) { // Adaptation Field Length int adaptationFieldLength = buffer.get() & 0xff; if (adaptationFieldLength != 0) { int remainingBytes = adaptationFieldLength; // Get next byte int nextByte = buffer.get() & 0xff; remainingBytes--; // Discontinuity indicator boolean discontinuityIndicator = (nextByte & 0x80) != 0; // Random Access indicator boolean randomAccessIndicator = (nextByte & 0x40) != 0; // Elementary stream priority indicator boolean elementaryStreamPriorityIndicator = (nextByte & 0x20) != 0; // PCR flag boolean pcrFlag = (nextByte & 0x10) != 0; // OPCR flag boolean opcrFlag = (nextByte & 0x08) != 0; // Splicing point flag boolean splicingPointFlag = (nextByte & 0x04) != 0; // Transport private data flag boolean transportPrivateDataFlag = (nextByte & 0x2) != 0; // Adaptation field extension flag boolean adaptationFieldExtensionFlag = (nextByte & 0x01) != 0; // PCR MTSPacket.AdaptationField.PCR pcr = null; if (pcrFlag) { pcr = parsePCR(); remainingBytes -= 6; } // OPCR MTSPacket.AdaptationField.PCR opcr = null; if (opcrFlag) { opcr = parsePCR(); remainingBytes -= 6; } // Splice countdown byte spliceCountdown = 0; if (splicingPointFlag) { spliceCountdown = buffer.get(); remainingBytes--; } byte[] privateData = null; if (transportPrivateDataFlag) { int transportPrivateDataLength = buffer.get() & 0xff; privateData = new byte[transportPrivateDataLength]; buffer.get(privateData); remainingBytes -= transportPrivateDataLength; } byte[] extension = null; if (adaptationFieldExtensionFlag) { int extensionLength = buffer.get() & 0xff; extension = new byte[extensionLength]; buffer.get(extension); remainingBytes -= extensionLength; } // Skip remaining bytes NIOUtils.skip(buffer, remainingBytes); adaptationField = new MTSPacket.AdaptationField(this, discontinuityIndicator, randomAccessIndicator, elementaryStreamPriorityIndicator, pcrFlag, opcrFlag, splicingPointFlag, transportPrivateDataFlag, adaptationFieldExtensionFlag, pcr, opcr, spliceCountdown, privateData, extension); } } this.transportErrorIndicator = transportErrorIndicator; this.payloadUnitStartIndicator = payloadUnitStartIndicator; this.transportPriority = transportPriority; this.pid = pid; this.scramblingControl = scramblingControl; this.adaptationFieldExist = adaptationFieldExist; this.containsPayload = containsPayload; this.continuityCounter = continuityCounter; this.adaptationField = adaptationField; // Payload this.payload = containsPayload ? buffer.slice() : null; } private AdaptationField.PCR parsePCR() { AdaptationField.PCR pcr; byte[] pcrBytes = new byte[6]; buffer.get(pcrBytes); long pcrBits = ((pcrBytes[0] & 0xffL) << 40) | ((pcrBytes[1] & 0xffL) << 32) | ((pcrBytes[2] & 0xffL) << 24) | ((pcrBytes[3] & 0xffL) << 16) | ((pcrBytes[4] & 0xffL) << 8) | (pcrBytes[5] & 0xffL); long base = (pcrBits & 0xFFFFFFFF8000L) >> 15; byte reserved = (byte) ((pcrBits & 0x7E00) >> 9); int extension = (int) (pcrBits & 0x1FFL); pcr = new AdaptationField.PCR(null, base, extension, reserved); return pcr; } public boolean isTransportErrorIndicator() { return transportErrorIndicator; } public void setTransportErrorIndicator(boolean transportErrorIndicator) { this.transportErrorIndicator = transportErrorIndicator; markDirty(); } public boolean isPayloadUnitStartIndicator() { return payloadUnitStartIndicator; } public void setPayloadUnitStartIndicator(boolean payloadUnitStartIndicator) { this.payloadUnitStartIndicator = payloadUnitStartIndicator; markDirty(); } public boolean isTransportPriority() { return transportPriority; } public void setTransportPriority(boolean transportPriority) { this.transportPriority = transportPriority; markDirty(); } public int getPid() { return pid; } public void setPid(int pid) { this.pid = pid; markDirty(); } public int getScramblingControl() { return scramblingControl; } public void setScramblingControl(int scramblingControl) { this.scramblingControl = scramblingControl; markDirty(); } public boolean isAdaptationFieldExist() { return adaptationFieldExist; } public void setAdaptationFieldExist(boolean adaptationFieldExist) { this.adaptationFieldExist = adaptationFieldExist; markDirty(); } public boolean isContainsPayload() { return containsPayload; } public void setContainsPayload(boolean containsPayload) { this.containsPayload = containsPayload; markDirty(); } public int getContinuityCounter() { return continuityCounter; } public void setContinuityCounter(int continuityCounter) { this.continuityCounter = continuityCounter; markDirty(); } public AdaptationField getAdaptationField() { return adaptationField; } public void setAdaptationField(AdaptationField adaptationField) { this.adaptationField = adaptationField; markDirty(); } public ByteBuffer getPayload() { return payload; } public void setPayload(ByteBuffer payload) { this.payload = payload; markDirty(); } }