forked from j62/ctbrec
607 lines
16 KiB
Java
607 lines
16 KiB
Java
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();
|
|
}
|
|
} |