NALU转JT1078的Java工具包
·
文章目录
Java NALU 转 JT/T 1078 媒体包工具实现
说明
JT1078 媒体包结构(视频包固定头部24字节 + NALU负载):
- 4字节魔数
0x30 0x31 0x63 0x64 - 1字节RTP标志位 V=2
- 2字节序列号
- 6字节BCD格式SIM卡号
- 1字节逻辑通道号
- 1字节帧分片标记(0不分片/1首包/3中间/2尾包)
- 8字节时间戳(毫秒)+帧间隔
- 2字节负载长度
- NALU裸流数据
完整工具类
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.nio.ByteOrder;
import java.util.concurrent.atomic.AtomicInteger;
/**
* NALU -> JT/T 1078 媒体数据包封装工具
* 支持:不分包、大包自动分片(MTU 950)
*/
public class Jt1078NaluEncoder {
// JT1078魔数 0x30316364
private static final int MAGIC = 0x30316364;
// JT视频固定头部长度 24字节
private static final int JT_HEAD_LEN = 24;
// 单包最大NALU负载长度(留头部空间)
private static final int MAX_NALU_PER_PACK = 950;
// 全局自增序列号
private final AtomicInteger seqGen = new AtomicInteger(0);
// SIM卡号BCD 12位字符串转6字节BCD数组
public static byte[] simToBcd(String sim) {
if (sim.length() != 12) {
throw new IllegalArgumentException("SIM必须12位数字");
}
byte[] bcd = new byte[6];
for (int i = 0; i < 6; i++) {
char c1 = sim.charAt(i * 2);
char c2 = sim.charAt(i * 2 + 1);
byte h = (byte) (c1 - '0');
byte l = (byte) (c2 - '0');
bcd[i] = (byte) ((h << 4) | l);
}
return bcd;
}
/**
* 封装NALU为JT1078媒体包列表(自动分片)
* @param nalu H264/H265完整NALU裸流
* @param simBcd 6字节BCD SIM卡
* @param channel 逻辑通道号 0~99
* @param frameInterval 帧间隔ms(如33)
* @return 多个JT1078数据包ByteBuf
*/
public ByteBuf[] encodeNaluToJt1078(byte[] nalu, byte[] simBcd, int channel, int frameInterval) {
int naluLen = nalu.length;
int sliceCount = (naluLen + MAX_NALU_PER_PACK - 1) / MAX_NALU_PER_PACK;
ByteBuf[] result = new ByteBuf[sliceCount];
long nowTs = System.currentTimeMillis();
for (int i = 0; i < sliceCount; i++) {
int offset = i * MAX_NALU_PER_PACK;
int sliceLen = Math.min(MAX_NALU_PER_PACK, naluLen - offset);
byte subFlag;
if (sliceCount == 1) {
subFlag = 0x00; // 不分包
} else if (i == 0) {
subFlag = 0x01; // 首包
} else if (i == sliceCount - 1) {
subFlag = 0x02; // 尾包
} else {
subFlag = 0x03; // 中间包
}
result[i] = buildSingleJtPacket(nalu, offset, sliceLen, simBcd, channel, subFlag, nowTs, frameInterval);
}
return result;
}
/**
* 构造单个JT1078媒体包
*/
private ByteBuf buildSingleJtPacket(byte[] fullNalu, int offset, int sliceLen,
byte[] simBcd, int channel, byte subFlag,
long tsMs, int frameInterval) {
int totalPacketLen = JT_HEAD_LEN + sliceLen;
ByteBuf buf = Unpooled.buffer(totalPacketLen).order(ByteOrder.BIG_ENDIAN);
// 1. 4字节魔数
buf.writeInt(MAGIC);
// 2. 1字节RTP标志 V=2
buf.writeByte(0x80);
// 3. 2字节序列号
int seq = seqGen.getAndIncrement() & 0xFFFF;
buf.writeShort(seq);
// 4. 6字节SIM BCD
buf.writeBytes(simBcd);
// 5. 1字节逻辑通道
buf.writeByte(channel);
// 6. 1字节分片标记(低4位分片,高4位帧类型0视频)
buf.writeByte(subFlag);
// 7. 8字节:时间戳(6)+帧间隔(2)
buf.writeLong((tsMs << 16) | (frameInterval & 0xFFFF));
// 8. 2字节NALU分片长度
buf.writeShort(sliceLen);
// 9. NALU分片数据
buf.writeBytes(fullNalu, offset, sliceLen);
return buf;
}
// 测试示例
public static void main(String[] args) {
Jt1078NaluEncoder encoder = new Jt1078NaluEncoder();
String sim12 = "13800138000";
byte[] simBcd = simToBcd(sim12);
// 模拟一段H264 NALU数据
byte[] testNalu = new byte[]{0x00,0x00,0x01,0x67,0x42,(byte)0x80,0x1E,0xDA,0x01};
ByteBuf[] jtPackets = encoder.encodeNaluToJt1078(testNalu, simBcd, 0, 33);
for (ByteBuf pkt : jtPackets) {
byte[] raw = new byte[pkt.readableBytes()];
pkt.readBytes(raw);
System.out.println("JT包长度:" + raw.length);
pkt.release();
}
}
}
配套UDP发送工具(发给JT1078平台)
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class Jt1078UdpSender {
private final DatagramSocket socket;
private final InetSocketAddress target;
public Jt1078UdpSender(String host, int port) throws Exception {
socket = new DatagramSocket();
target = new InetSocketAddress(host, port);
}
public void sendPacket(byte[] jtData) throws Exception {
DatagramPacket dp = new DatagramPacket(jtData, jtData.length, target);
socket.send(dp);
}
public void close() {
socket.close();
}
}
使用流程示例
// 1. 初始化编码器
Jt1078NaluEncoder encoder = new Jt1078NaluEncoder();
byte[] simBcd = Jt1078NaluEncoder.simToBcd("13912345678");
// 2. UDP发送器,连接JT1078流媒体服务端口
Jt1078UdpSender udpSender = new Jt1078UdpSender("127.0.0.1", 8000);
// 循环收到H264 NALU裸流 byte[] nalu
while (true) {
byte[] nalu = getH264Nalu();
// 封装为JT1078包数组(自动分片)
ByteBuf[] jtBufs = encoder.encodeNaluToJt1078(nalu, simBcd, 0, 33);
for (ByteBuf buf : jtBufs) {
byte[] pkt = new byte[buf.readableBytes()];
buf.readBytes(pkt);
udpSender.sendPacket(pkt);
buf.release();
}
Thread.sleep(30);
}
关键适配要点
- 分片机制:NALU超过950字节自动拆分包,头部标记首/中/尾包,平台才能拼接完整帧;
- SIM卡必须12位数字转6字节BCD,JT1078强制;
- 输出UDP包可直接发给支持JT1078的流媒体服务(ZLMediaKit JT1078端口);
- 序列号全局自增,uint16循环溢出自动处理;
- 时间戳使用系统毫秒,兼容国标时间格式;
- 若需要反向 JT1078包解析为NALU,可以配套写解码工具。
依赖
仅依赖 Netty buffer(工程已有Netty直接用,无Netty可替换byte数组手动拼接)
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>4.1.94.Final</version>
</dependency>
无Netty纯原生byte数组版本
如需脱离Netty,我可以给你纯byte[]拼接版本,不引入任何第三方包。
更多推荐

所有评论(0)