package cn.org.hentai.jtt1078.server.video;
|
|
import cn.org.hentai.jtt1078.entity.Analyze;
|
import cn.org.hentai.jtt1078.entity.enums.ProtocolVersion;
|
import cn.org.hentai.jtt1078.util.ByteHolder;
|
import cn.org.hentai.jtt1078.util.ByteUtils;
|
import cn.org.hentai.jtt1078.util.Packet;
|
import lombok.extern.slf4j.Slf4j;
|
|
import java.time.Instant;
|
import java.time.ZoneOffset;
|
import java.time.ZonedDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.Arrays;
|
|
/**
|
* JTT1078 Protocol Decoder
|
*/
|
@Slf4j
|
public class Jtt1078Decoder {
|
private final ByteHolder buffer = new ByteHolder(4096);
|
private static final byte[] HEAD1078 = {0x30, 0x31, 0x63, 0x64};
|
private ProtocolVersion protocolVersion = ProtocolVersion.UNKNOWN;
|
|
public void write(byte[] block) {
|
buffer.write(block);
|
}
|
|
public void write(byte[] block, int startIndex, int length) {
|
byte[] buff = new byte[length];
|
System.arraycopy(block, startIndex, buff, 0, length);
|
write(buff);
|
}
|
|
public Packet decode() {
|
|
if (buffer.size() < 4) return null;
|
|
// Check header
|
if (!Arrays.equals(buffer.array(4), HEAD1078)) {
|
// log.warn("Invalid protocol header, expected:30316364, actual:{}",
|
// ByteUtils.toHexString(buffer.array(4)));
|
buffer.clear();
|
return null;
|
}
|
|
Packet packet;
|
// 第一次自动识别版本
|
if (protocolVersion == ProtocolVersion.UNKNOWN) {
|
packet = autoDetectVersionAndParse();
|
} else {
|
packet = decodePacket(protocolVersion);
|
|
}
|
return packet;
|
}
|
|
// 自动检测版本并解析
|
private Packet autoDetectVersionAndParse() {
|
// 尝试 2013 版本
|
Packet packet = decodePacket(ProtocolVersion.V2013);
|
if (packet != null) {
|
protocolVersion = ProtocolVersion.V2013;
|
Analyze analyze = parse16(packet.getBytes());
|
System.out.printf("%s-%d device started output%n", analyze.getSim(), analyze.getCh());
|
return packet;
|
}
|
|
// 尝试 2019 版本
|
packet = decodePacket(ProtocolVersion.V2019);
|
if (packet != null) {
|
protocolVersion = ProtocolVersion.V2019;
|
Analyze analyze = parse19(packet.getBytes());
|
System.out.printf("%s-%d device started output%n", analyze.getSim(), analyze.getCh());
|
return packet;
|
}
|
return null;
|
}
|
|
// 根据指定版本解析
|
private Packet decodePacket(ProtocolVersion version) {
|
if (version == ProtocolVersion.UNKNOWN) {
|
return null;
|
}
|
|
int dataTypeIndex = (version == ProtocolVersion.V2013) ? 15 : 19;
|
if (buffer.size() < dataTypeIndex + 1) return null;
|
|
byte dataType = (byte) ((buffer.get(dataTypeIndex) >> 4) & 0x0F);
|
int dataLenIndex;
|
|
switch (dataType) {
|
case 3:
|
dataLenIndex = dataTypeIndex + 9;
|
break;
|
case 4:
|
dataLenIndex = dataTypeIndex + 1;
|
break;
|
default:
|
dataLenIndex = dataTypeIndex + 13;
|
}
|
//log.info("dataType:"+dataType);
|
if (buffer.size() < dataLenIndex + 2) return null;
|
int dataLen = buffer.getShort(dataLenIndex) & 0xFFFF;
|
int packetLength = dataLenIndex + 2 + dataLen;
|
|
if (buffer.size() < packetLength) return null;
|
|
// Check next packet header
|
if (buffer.size() >= packetLength + 4) {
|
byte[] nextHeader = new byte[4];
|
for (int i = 0; i < 4; i++) {
|
nextHeader[i] = buffer.get(packetLength + i);
|
}
|
if (!Arrays.equals(nextHeader, HEAD1078)) {
|
//log.warn("Invalid data type: {}", dataType);
|
buffer.clear();
|
return null;
|
}
|
}
|
|
byte[] packetData = new byte[packetLength];
|
buffer.sliceInto(packetData, packetLength);
|
|
//printPacketInfo(packetData, version);
|
|
return Packet.create(packetData);
|
}
|
|
// 解析16版本数据
|
private Analyze parse16(byte[] data) {
|
Analyze analyze = new Analyze();
|
analyze.setPt((byte) (data[5] & 0x7F));
|
analyze.setSn((short) (((data[6] & 0xFF) << 8) | (data[7] & 0xFF)));
|
|
StringBuilder simBuilder = new StringBuilder();
|
for (int i = 0; i < 6; i++) {
|
simBuilder.append(nextBcd(data, 8 + i));
|
}
|
analyze.setSim(simBuilder.toString());
|
|
analyze.setCh(data[14]);
|
analyze.setDt((byte) ((data[15] >> 4) & 0x0F));
|
analyze.setFi((byte) (data[15] & 0x0F));
|
|
int dataLenIndex;
|
switch (analyze.getDt()) {
|
case 3:
|
analyze.setTimestamp(setTimestamp(data, 16));
|
dataLenIndex = 24;
|
break;
|
case 4:
|
dataLenIndex = 16;
|
break;
|
default:
|
analyze.setTimestamp(setTimestamp(data, 16));
|
analyze.setLastIInterval((short) (((data[24] & 0xFF) << 8) | (data[25] & 0xFF)));
|
analyze.setLastInterval((short) (((data[26] & 0xFF) << 8) | (data[27] & 0xFF)));
|
dataLenIndex = 28;
|
}
|
|
analyze.setDataLen((short) (((data[dataLenIndex] & 0xFF) << 8) | (data[dataLenIndex + 1] & 0xFF)));
|
byte[] payload = new byte[analyze.getDataLen()];
|
System.arraycopy(data, dataLenIndex + 2, payload, 0, payload.length);
|
analyze.setData(payload);
|
|
return analyze;
|
}
|
|
// 解析19版本数据
|
private Analyze parse19(byte[] data) {
|
Analyze analyze = new Analyze();
|
analyze.setPt((byte) (data[5] & 0x7F));
|
analyze.setSn((short) (((data[6] & 0xFF) << 8) | (data[7] & 0xFF)));
|
|
StringBuilder simBuilder = new StringBuilder();
|
for (int i = 0; i < 10; i++) {
|
simBuilder.append(nextBcd(data, 8 + i));
|
}
|
analyze.setSim(simBuilder.toString());
|
|
analyze.setCh(data[18]);
|
analyze.setDt((byte) ((data[19] >> 4) & 0x0F));
|
analyze.setFi((byte) (data[19] & 0x0F));
|
|
int dataLenIndex;
|
switch (analyze.getDt()) {
|
case 3:
|
analyze.setTimestamp(setTimestamp(data, 20));
|
dataLenIndex = 28;
|
break;
|
case 4:
|
dataLenIndex = 20;
|
break;
|
default:
|
analyze.setTimestamp(setTimestamp(data, 20));
|
analyze.setLastIInterval((short) (((data[28] & 0xFF) << 8) | (data[29] & 0xFF)));
|
analyze.setLastInterval((short) (((data[30] & 0xFF) << 8) | (data[31] & 0xFF)));
|
dataLenIndex = 32;
|
}
|
|
analyze.setDataLen((short) (((data[dataLenIndex] & 0xFF) << 8) | (data[dataLenIndex + 1] & 0xFF)));
|
byte[] payload = new byte[analyze.getDataLen()];
|
System.arraycopy(data, dataLenIndex + 2, payload, 0, payload.length);
|
analyze.setData(payload);
|
|
return analyze;
|
}
|
|
private long setTimestamp(byte[] data, int offset) {
|
long timestamp = 0;
|
for (int i = 0; i < 8; i++) {
|
timestamp = (timestamp << 8) | (data[offset + i] & 0xFF);
|
}
|
return timestamp;
|
}
|
|
private String nextBcd(byte[] data, int offset) {
|
byte val = data[offset];
|
int ch1 = (val >> 4) & 0x0F;
|
int ch2 = val & 0x0F;
|
return String.format("%d%d", ch1, ch2);
|
}
|
|
|
private void printPacketInfo(byte[] packet, ProtocolVersion version) {
|
// System.out.println("Raw Packet (Hex):\n" + ByteUtils.toHexString(packet));
|
|
System.out.println("\n=== JTT1078 " + ((version == ProtocolVersion.V2013) ? "2013" : "2019") + " Protocol " +
|
"Packet ===");
|
System.out.println("Header: " + ByteUtils.toHexString(packet, 0, 4));
|
|
Analyze analyze = (version == ProtocolVersion.V2013) ? parse16(packet) : parse19(packet);
|
System.out.println("Payload Type: " + analyze.getPt());
|
System.out.println("Sequence Number: " + analyze.getSn());
|
System.out.println("SIM: " + analyze.getSim());
|
System.out.println("Channel: " + analyze.getCh());
|
String dataType;
|
switch (analyze.getDt()) {
|
case 0:
|
dataType = "Video I Frame";
|
break;
|
case 1:
|
dataType = "Video P Frame";
|
break;
|
case 2:
|
dataType = "Video B Frame";
|
break;
|
case 3:
|
dataType = "Audio Frame";
|
break;
|
case 4:
|
dataType = "Transparent Data";
|
break;
|
default:
|
dataType = "Unknown";
|
break;
|
}
|
System.out.println("Data Type: " + dataType);
|
String FrameInfo;
|
switch (analyze.getFi()) {
|
case 0:
|
FrameInfo = "Atomic Packet";
|
break;
|
case 1:
|
FrameInfo = "First Packet";
|
break;
|
case 2:
|
FrameInfo = "Last Packet";
|
break;
|
case 3:
|
FrameInfo = "Middle Packet";
|
break;
|
default:
|
FrameInfo = "Unknown (" + analyze.getFi() + ")";
|
break;
|
}
|
System.out.println("Frame Info: " + FrameInfo);
|
|
if (analyze.getTimestamp() != 0) {
|
Instant instant = Instant.ofEpochMilli(analyze.getTimestamp());
|
ZonedDateTime beijingTime = instant.atZone(ZoneOffset.ofHours(8));
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
System.out.println("Timestamp: " + formatter.format(beijingTime));
|
}
|
|
if (analyze.getLastIInterval() != 0) {
|
System.out.println("Last I Frame Interval: " + analyze.getLastIInterval());
|
}
|
if (analyze.getLastInterval() != 0) {
|
System.out.println("Last Frame Interval: " + analyze.getLastInterval());
|
}
|
|
System.out.println("Data Length: " + analyze.getDataLen());
|
System.out.println("=================================\n");
|
|
}
|
}
|