简介JGB28181是一套基于Java语言实现的GB28181国标平台源码面向安防监控、视频流媒体开发方向的Java工程师与学习者帮助理解国标信令交互与实时视频流传输的落地实现。资源包共49个文件以43个java源码为核心辅以2个xml配置、1个properties参数文件、1个yml配置及gitignore、md说明文档整体压缩包约64KB体量轻便便于快速导入IDE阅读与调试。使用前只需修改config.properties中的配置信息编译运行即可启动。功能上已实现设备注册、注册恢复、目录查询以及TCP被动与UDP两种模式的实时视频流覆盖GB28181基础信令与媒体传输链路。目前已有4241人学习下载适合作为国标平台二次开发、协议对接与课程设计的参考底稿也可用于排查注册与流媒体交互中的常见问题。1. 从一次对接失败说起为什么我盯上了这个 Java 版 GB28181 平台去年帮一个做安防集成的朋友排查问题现场十几台摄像机上级平台怎么都收不到视频流。抓包一看SIP 信令通了但 INVITE 里的 SDP 协商对不上下级设备回的媒体端口和平台侧预期差了整整一个段。那会儿用的是某 C 系开源方案配置散在七八个文件里改一个参数要重启三个服务。后来在代码托管平台上翻到 JGB28181 这个项目——纯 Java 写的 GB28181 平台SIP 信令和媒体流处理都在一个工程里配置集中二次开发不用在 C 和 Java 之间来回切。GB28181 本身是安防设备联网的国标协议核心就是 SIP 做信令、RTP/RTCP 跑媒体JGB28181 把这两块用 Java 生态重新实现了一遍适合需要把监控设备接入自有业务系统的团队也适合想拿一套可读代码理解 GB28181 完整交互流程的开发者。Java 技术栈意味着你能用 Spring 那套东西去扩展它而不是对着 Makefile 发愁。2. 架构拆解SIP 信令层和媒体流层是怎么分工的2.1 整体分层与模块职责JGB28181 的工程结构基本遵循 GB28181 协议的分层逻辑。最上面是接入层负责和下级设备摄像机、NVR以及上级平台建立 SIP 会话中间是信令控制层处理 REGISTER、INVITE、ACK、BYE、MESSAGE 这些 SIP 方法底下是媒体层管 RTP 收流、转封装、按需推流。项目里通常能看到这么几个包sip包放 SIP 栈的封装和监听器media包处理 RTP 会话和流媒体转发service包是业务逻辑config包集中管理平台 ID、域、端口这些参数。和很多 C 系方案把 SIP 栈和媒体服务器拆成两个进程不同JGB28181 倾向于在一个 JVM 里把信令和媒体都管起来。好处是调试时一个断点能从 SIP 消息直接跟到 RTP 包坏处是媒体转发量大时要注意线程模型。我一般会先看config里的平台配置类把 SIP 监听端口、媒体端口范围、上级平台地址这几个关键参数摸清楚再顺着sip包的监听器往里读。2.2 SIP 信令注册与心跳处理GB28181 设备接入的第一步是 REGISTER设备带着平台 ID、设备 ID 和鉴权信息过来平台校验通过后返回 200 OK之后设备定期发 MESSAGE 心跳。JGB28181 里这块通常由一个 SIP 监听器加一个注册服务类配合完成。下面是一段典型的注册处理逻辑我按项目里常见的写法还原// SipRegisterListener.java public void processRegister(RequestEvent event) { Request request event.getRequest(); String deviceId SipUtils.getDeviceId(request); // 从 From 头提取设备编码 String platformId SipUtils.getPlatformId(request); // 从 To 头提取平台编码 // 校验设备是否在允许接入的白名单里 if (!deviceRegistry.isAllowed(deviceId)) { // 不在白名单直接回 403避免非法设备占用资源 response.sendError(Response.FORBIDDEN); return; } // 首次注册走鉴权后续注册刷新有效期 if (deviceRegistry.isFirstRegister(deviceId)) { String nonce authService.generateNonce(deviceId); // 回 401 带 WWW-Authenticate等设备带鉴权信息重试 response.sendChallenge(nonce); } else { // 鉴权通过记录设备地址和过期时间 deviceRegistry.update(deviceId, request.getRemoteAddress(), 3600); response.sendOk(); } }这段代码的关键在deviceRegistry的状态管理设备第一次来要发 401 挑战设备带Authorization头重试才放行。nonce是一次性随机串防止重放。过期时间一般设 3600 秒设备会在过期前重新注册。心跳处理则简单得多收到 MESSAGE 后解析 XML body更新设备的lastHeartbeat时间戳超时没心跳就在注册表里标记离线。常见做法是用一个定时任务扫注册表把超时设备清掉避免无效设备一直占着会话。2.3 媒体流协商与 RTP 收流信令通了之后真正的视频流靠 INVITE 里的 SDP 协商。平台侧发 INVITE 给设备SDP 里带上平台收流的 IP 和端口设备回 200 OK 时把自己的媒体描述带回来。JGB28181 里媒体层通常用一个MediaSession类来管理每个会话的 RTP 接收。// MediaSession.java public class MediaSession { private DatagramSocket rtpSocket; // 收 RTP 的 UDP socket private DatagramSocket rtcpSocket; // 收 RTCP 的 UDP socket private int localPort; // 平台侧收流端口 private String remoteIp; // 设备侧发流地址 private int remotePort; // 设备侧发流端口 public void start() throws SocketException { // 端口从配置的媒体端口范围里取避免和系统端口冲突 rtpSocket new DatagramSocket(localPort); rtcpSocket new DatagramSocket(localPort 1); // 起两个线程分别收 RTP 和 RTCP new Thread(this::receiveRtp).start(); new Thread(this::receiveRtcp).start(); } private void receiveRtp() { byte[] buffer new byte[1500]; // 一个 MTU 大小避免 IP 分片 while (running) { DatagramPacket packet new DatagramPacket(buffer, buffer.length); rtpSocket.receive(packet); // 解析 RTP 头拿到 SSRC 和序列号 RtpPacket rtp RtpParser.parse(packet.getData()); // 按 SSRC 分发到对应的流处理器 streamDispatcher.dispatch(rtp); } } }这里有几个参数值得注意localPort一般从配置的端口范围里动态分配比如 30000 到 40000避免多个会话撞端口buffer设 1500 是因为 RTP 包通常不超过 MTU设大了浪费内存设小了会截断SSRC是流的唯一标识同一个设备可能有多路流靠 SSRC 区分。RTCP 端口习惯上取 RTP 端口加一这是 GB28181 里常见的约定。收上来的 RTP 包要么直接转发给上级平台要么解封装后送去做转码或存文件取决于业务需求。3. 跑起来从零启动一个可注册的 GB28181 平台3.1 环境准备与依赖检查JGB28181 是 Java 工程跑之前先把 JDK 和构建工具备好。项目一般用 Maven 管理依赖JDK 版本看pom.xml里的maven.compiler.source常见是 8 或 11。除了 JDK还要确认本机防火墙没挡住 SIP 和媒体端口——这是新手最容易翻车的地方信令端口通了但媒体端口被拦现象就是设备注册成功但视频黑屏。# 检查 JDK 版本确保和 pom.xml 里声明的一致 java -version # 编译打包跳过测试加快首次构建 mvn clean package -DskipTests # 看打包产物通常是一个可执行 jar 或 war ls target/如果mvn package报依赖下载失败先看 Maven 的settings.xml里镜像配没配对。国内环境常见做法是配一个公共镜像仓库不然拉依赖能等到怀疑人生。打包成功后target目录下会有jgb28181-xxx.jar或者一个带依赖的 fat jar具体看pom.xml里有没有配maven-shade-plugin或spring-boot-maven-plugin。3.2 关键配置项说明启动前必须改的配置集中在application.yml或config.properties里。下面这张表是我从项目配置类里整理出来的核心参数改错一个就连不上配置项含义常见值注意点sip.ip平台 SIP 监听地址本机内网 IP不能写 127.0.0.1设备连不上sip.portSIP 信令端口5060被占用就换设备侧要同步改sip.platformId平台编码20 位国标编码和上级平台约定一致sip.domainSIP 域平台编码前 10 位设备注册时校验用media.portRange媒体端口范围30000-40000范围要够大并发会话多时别不够分media.ip媒体收流地址本机内网 IP多网卡时写对外的那个sip.ip和media.ip这两个最容易填错。有些开发者在本机测试时填了127.0.0.1结果设备发流到回环地址平台收不到。正确做法是填本机在局域网里的实际 IP设备能路由到的那个。platformId是 20 位编码前 8 位是行政区划中间 2 位是行业后 10 位是设备序号这个编码规则在 GB28181 里有明确定义配的时候别自己瞎编。3.3 启动与设备注册验证配置改完就可以启动了。如果是 Spring Boot 工程直接java -jar跑如果是普通 Java 工程看有没有main方法或者启动脚本。# 启动平台指定配置文件位置 java -jar target/jgb28181-1.0.jar --spring.config.location./config/application.yml # 看日志里 SIP 栈有没有起来 tail -f logs/jgb28181.log | grep -i sip server started平台起来后拿一台支持 GB28181 的摄像机或 NVR 来注册。设备侧要填平台 IP、SIP 端口、平台编码、设备编码和鉴权密码。注册成功后平台日志里会打出REGISTER请求和200 OK响应。如果设备一直注册不上按这个顺序排查先telnet平台 SIP 端口通不通再看设备侧填的平台 IP 是不是平台实际监听的 IP最后抓包看 SIP 消息有没有到平台。我一般会在平台侧开一个tcpdump抓 5060 端口设备一发注册就能看到包到没到比翻日志快。4. 避坑排查设备注册不上、视频黑屏、端口冲突怎么解4.1 设备注册返回 401 后没下文现象是设备发 REGISTER平台回 401 挑战然后设备就不重试了。原因通常是设备侧没配鉴权密码或者密码和平台侧存的对不上。GB28181 的鉴权是摘要认证设备拿 nonce 和密码算一个 response 带回来平台用同样的算法验。解决方法是检查设备配置里的 SIP 密码字段确保和平台deviceRegistry里存的一致。有些设备对 nonce 的编码方式有要求平台侧生成 nonce 时别带特殊字符纯十六进制字符串最稳。4.2 注册成功但视频流收不到这是最典型的翻车场景。信令全通INVITE 也回了 200 OK但平台侧 RTP 端口一个包都没收到。原因一般有三个一是media.ip填成了回环地址设备往 127.0.0.1 发流平台自然收不到二是防火墙拦了 UDP 媒体端口SIP 走 TCP 能通但 RTP 走 UDP 被挡三是 SDP 里协商的端口和实际收流端口不一致设备按 SDP 里的端口发平台却在另一个端口收。排查时先在平台侧tcpdump udp port 30000看有没有包进来有包就是解析问题没包就是网络或配置问题。4.3 媒体端口范围不够导致新会话失败并发路数一多媒体端口就不够分了。现象是前几路正常后面几路 INVITE 成功但收不到流。原因是media.portRange设得太窄比如只给了 30000-30010十路以上就没了。解决方法是把范围拉大同时确认操作系统没限制 UDP 端口数量。另外端口分配后要及时释放会话结束时把端口还回池子不然跑一段时间端口就耗尽了。常见做法是用一个ConcurrentHashMap记录端口占用状态分配时加锁释放时移除。4.4 上级平台收流花屏或卡顿平台把流转发给上级时出现花屏多半是 RTP 包顺序乱了或者丢了。原因可能是转发线程处理不过来UDP 缓冲区溢出。解决方法是调大 socket 的接收缓冲区rtpSocket.setReceiveBufferSize(1024 * 1024)同时检查转发逻辑有没有阻塞。如果用了线程池确认核心线程数够用别让 RTP 包在队列里排队。还有一种可能是 MTU 问题RTP 包超过 1500 字节被分片接收端重组失败这种情况要确认设备侧的编码参数别让单包太大。5. 二次开发扩展设备管理和流媒体转发的几个入手点拿到 JGB28181 之后最常改的两个方向是设备管理扩展和流媒体转发定制。设备管理这块项目里通常有一个DeviceRegistry类维护在线设备列表你可以在这里加数据库持久化把设备信息落到 MySQL 或 PostgreSQL重启后不用等设备重新注册。我一般会在update方法里加一个异步写库的操作别阻塞 SIP 线程。设备状态变更时发个事件业务层监听事件做后续处理这样扩展性最好。流媒体转发这块默认实现可能是直接 RTP 转发但实际业务往往需要转成 HTTP-FLV 或 HLS 给 Web 端播放。入手点是MediaSession里的streamDispatcher它拿到 RTP 包后你可以选择转发给上级、写文件、或者推给流媒体服务器。如果要做 Web 播放常见做法是接一个 ZLMediaKit 或 SRS 这样的流媒体服务JGB28181 侧把 RTP 包通过 UDP 推给流媒体服务由它负责转协议。改的时候注意别在 RTP 接收线程里做耗时操作收包和转发解耦用队列缓冲。验证改动是否生效我习惯用两个手段一是单元测试模拟 SIP 消息直接调监听器方法看响应对不对二是起一个模拟设备用脚本发 REGISTER 和 INVITE观察平台侧日志和收流情况。模拟设备可以用 Python 的socket库手写 SIP 消息比真机调试快得多。改完媒体转发后用ffplay拉一下流看画面正不正常比看日志直观。从那以后我每次改 SIP 相关代码都强制先跑一遍注册和 INVITE 的模拟流程确认信令层没被改坏再动媒体层。这套习惯帮我省了不少联调时间。希望帮到你。本文还有配套的精品资源点击获取