简介:JGB28181是一套基于Java实现的GB28181国标信令平台源码,面向安防视频监控开发者、系统集成商及需要对接国标设备的学生工程师。项目核心涵盖设备注册、目录查询、实时视频流(TCP被动/UDP)等基本功能,修改config.properties配置文件即可完成本地部署,适合用作国标协议学习与实际项目二次开发的基础框架。资源包共49个文件,以43个Java源文件为主,辅以2个XML配置、1个properties属性文件、1个yml配置及README文档;压缩包仅64KB,结构精简,便于快速阅读核心信令逻辑与配置项。简介还保留了功能更新日志与国标QQ交流群信息,方便使用者跟进版本变化。目前已有4241人浏览学习,说明其在国标接入场景中具备参考价值。下载后可直接获取完整工程源码,通过研读注册、目录查询、实时流会话建立等代码实现,能加深对GB28181协议交互过程的理解,并为自研业务系统嵌入国标能力提供可运行的最小示例。
1. 国标接入的 Java 解法:JGB28181 平台到底解决了什么
干过视频监控对接的人都知道,最折磨人的不是镜头装歪了,而是不同厂家的设备 SDK 各有一套,今天调海康的 SDK,明天调大华的 SDK,后天又来一个不知名厂家的私有协议,时间全耗在适配上了。某次做一个跨区域联网项目,现场接了三种不同品牌的 NVR 和 IPC,光联调就折腾了两周,后来换了思路,让所有设备走 GB28181 国标协议接入一个统一的 Java 平台,两天搞定。JGB28181 就是这一类基于 Java 实现的国标平台,核心是把 SIP 信令、RTP 媒体流、设备目录管理这些本来要逐个对接的脏活统一收口,让上层业务只关注一件事:拿到标准化的设备树和实时视频流。适合被私有 SDK 折磨过的后端开发,也适合要快速搭建国标监控平台的团队。
2. 先弄清国标接入的基本盘:SIP 信令与 RTP 媒体流的职责边界
2.1 国标 28181 不是协议,是一套会话规矩
第一次接触 GB28181 的人容易误以为它是一个像 HTTP 那样的“协议”,上手后才发现它是一整套“会话规矩”。国标的核心思路是把视频监控的联网通信拆成两条独立通道:一个是信令通道,负责设备注册、目录查询、实时点播的请求与响应,走的叫 SIP(Session Initiation Protocol);另一个是媒体通道,负责实际传输音视频码流,通常走 RTP。信令通道像是打电话时“先拨号、等待接通”的过程,媒体通道是接通后“说话”的过程,两者分离但状态关联。
平台侧的角色也由此划分:需要有一个 SIP 服务端来接收设备上报的注册消息,维护设备在线状态;同时要有一个媒体接收模块,监听 RTP 端口,把设备推上来的码流解析出来,再转交给上层的流媒体服务或播放器。JGB28181 在 Java 生态里做这件事,核心依赖的是 SIP 协议的 Java 实现库和 RTP 的底层 Socket 收发能力。选 Java 而不是 C++ 或 Go,优势在于后端团队能直接用熟悉的 Spring 体系去做业务扩展,设备管理、权限控制、录像检索这些功能能快速长出来。
设备接入的过程可以分成三步理解:设备上线时先向平台的 SIP 端口发送注册请求,平台回 200 OK,设备进入在线状态;业务系统要看到某路视频时,平台向设备发 INVITE 请求,设备回 200 OK 后开始往指定 IP 和端口推 RTP 流;平台收到 RTP 流后解析封装,转成可播放的格式。这套流程在所有国标设备上一致,这也是平台能替代私有 SDK 的根本原因。
2.2 平台模块怎么划分:信令服务、媒体收流、业务接口三块各管各的
看一个国标平台实现得好不好,先看它的模块边界是否清晰。标准的 JGB28181 平台在代码层面会分成三大模块。
第一个模块是信令服务,负责启动一个基于 Java 的 SIP 监听端点,通常监听在 5060 端口(UDP)。这个模块处理设备注册、注销、心跳保活、目录查询请求、云台控制指令,核心工作是把 SIP 消息解析成内部事件对象,再交给业务层处理。第二个模块是媒体收流,负责监听一组 RTP 端口,当设备推流时接收数据包,解析 PS 封装(Program Stream),提取 H.264 或 H.265 的视频帧,然后通过转封装或直接转发的方式输出。这里的关键是端口管理和码流平滑处理,端口分配策略决定了并发路数的上限。第三个模块是业务接口层,是给上层系统用的。基于 Spring Boot 暴露 HTTP API,比如查询设备列表、发起实时点播、停止点播、查询录像文件列表。这三个模块在 JGB28181 里是分开实现的,信令模块不直接碰媒体数据,媒体模块也不关心业务逻辑。
理解这个概念后,再回头看“平台”这两个字的含义——它不是简单的视频播放器,而是一个信令与媒体分离的中间层。设备厂商不再需要暴露私有 SDK 给集成商,只需要支持国标协议;上层业务系统也不关心设备品牌,只需要调用平台接口。技术栈上,Java 的并发模型天然适合同时处理大量设备的信令交互,一个设备一个注册会话,SIP 事务的状态管理用 ConcurrentHashMap 就能很好地处理。
2.3 代码里看信令接入:基于 Java 的 SIP 注册处理核心逻辑
直接看代码最能说明问题。JGB28181 的注册处理可以抽象成一个监听器,关键代码结构如下:
@Component public class SIPRegisterHandler { private final DeviceRegistry deviceRegistry; public SIPRegisterHandler(DeviceRegistry deviceRegistry) { this.deviceRegistry = deviceRegistry; } public void handleRegister(SipRequest request) { // 从 SIP 消息的 From 头解析设备 ID,国标设备的编码是 20 位数字 String deviceId = extractDeviceIdFromHeaders(request); // 从 Contact 头解析设备上报的 IP 和端口,后续回复和发流都用这个地址 InetSocketAddress deviceAddress = extractContactAddress(request); // 校验设备 ID 格式:20 位数字,中心编码 + 类型 + 序号 if (!isValidDeviceId(deviceId)) { // 日志记录非法设备 ID,不回复 200 OK,设备会因超时自动重试 log.warn("收到非法设备ID {}", deviceId); return; } // 构造 SIP 200 OK 响应,带上 Via、From、To 等头字段 SipResponse okResponse = buildOkResponse(request); // 向设备地址发送响应 sipSender.sendResponse(okResponse, deviceAddress); // 更新平台内的设备在线状态表,记录最近注册时间 deviceRegistry.updateOnlineStatus(deviceId, deviceAddress); // 国标要求注册成功后必须立刻发一条目录查询请求,拿到设备下的通道列表 sendCatalogQuery(deviceId); } }这段逻辑里两个细节比较关键:设备 ID 的 20 位编码是国标的基础约定,比如中心编码 8 位加上设备类型码 2 位加上行业编码 2 位再加序号,校验这一步不能省,否则垃圾消息会污染设备表;注册成功后立刻发目录查询,这是国标的标准交互顺序。另一个细节是用ConcurrentHashMap维护设备地址与 ID 的映射,因为 SIP 是无连接的,每次请求都要靠设备 ID 来定位回复地址。实际项目中,设备地址要保存最后上报的地址,而不是注册表里首次记录的地址,因为很多设备在心跳里会更新自己的 IP 和端口。
2.4 媒体接收通道的端口规划与线程模型
设备推流是另一个独立的体系。在 JGB28181 的实现里,RTP 收流端口需要提前规划,不能每路流都随便占用一个端口。常见做法是配置一个端口范围,比如从 30000 到 40000,每路点播会话从范围里分配一个未被占用的端口。这个范围决定了平台能同时处理多少路实时流,如果范围太小,会出现点播报错“无法分配端口”的情况。
媒体模块的线程模型也需要注意。每收一路流就创建一个线程的写法在并发高时会崩溃,正确做法是使用 Netty 的 EventLoop 线程组来处理 RTP 数据包,数据解析用独立的工作线程池。数据结构上,每路流用一个会话对象维护当前收到的 RTP 序列号和 PS 包的组装状态,同一个设备的多次点播也要分配不同的会话,避免串流。另外,RTP 包中 PS 封装的解析要注意时间戳的处理,不处理时间戳直接转发的方案会造成播放器无法倍速拖动,处理时间戳的方案则需要维护一个映射缓存。
3. 把 JGB28181 平台跑起来:从环境准备到一路现场视频点播
3.1 部署环境的硬性要求:JDK 版本、数据库选型与端口清单
从零部署 JGB28181 平台需要准备的环境比想象中要简单,但也有些版本上的硬性要求。JDK 建议使用 8 或 11 版本,国标项目里大量依赖的 SIP 库和 Netty 组件在这些版本上表现最稳定,某些开箱即用的实现里打包的依赖还停留在 javax 命名空间,直接跑在 17 上会报模块访问错误。数据库用 MySQL 5.7 或 8.0 即可,平台的主要存储内容是设备表、通道表、录像索引,量级不大,不需要分库分表。Redis 是可选项,如果追求设备状态能在多实例间共享,就用它存状态;单机部署可以省掉。
端口规划上,最低需要三个端口范围:信令模块的 SIP 端口(默认 5060,UDP),媒体收流端口范围(建议至少 100 个连续 UDP 端口),以及业务接口的 HTTP 端口(常用 8080)。生产环境部署时还有一个容易忽略的点:SIP 信令走 UDP,意味着防火墙要放行 UDP 协议而不仅是 TCP;如果只放了 TCP,设备能注册但不能收流,因为 RTP 也是 UDP 承载。遇到过最典型的问题是云服务器安全组默认全开 TCP,GB28181 设备的注册报文发进来就被丢弃,表现就是设备一直在“注册中”。
3.2 初始化配置:application.yml 里的参数逐项说明
平台启动前需要改的核心配置在application.yml里。以下是我在模拟项目X里使用的配置模板,逐条说明参数含义:
server: port: 8080 # 业务 API 的 HTTP 端口,给上层系统调用 sip: server: ip: 192.168.1.10 # 平台的 SIP 服务对外地址,必须填实际网卡 IP,不能填 0.0.0.0 port: 5060 # SIP UDP 监听端口,国标固定为 5060,一般不改 domain: 34020000002000000001 # SIP 域,20 位国标编码,标识本平台身份 password: 12345678 # 设备注册时校验的密码,设备端要配置一致 media: rtp: port-range-start: 30000 # 媒体收流起始端口 port-range-end: 40000 # 媒体收流结束端口,动态分配给每路点播会话 packet-session-timeout: 30 # 收流会话超时时间,超过 30 秒没收到包则释放会话 catalog: auto-query-on-register: true # 设备注册后自动发起目录查询,拉取通道列表 mysql: url: jdbc:mysql://127.0.0.1:3306/gb28181?useUnicode=true&characterEncoding=utf8 username: root password: your_password logging: level: com.jgb28181: debug # 建议启动排查时开 debug 级日志domain这一项是很多人配错的。它标识的是平台自身在国标网络里的编码,设备注册时会把From头里的域字段和平台配置比对,不一致会拒绝注册。另外media.packet-session-timeout直接影响资源回收速度。参数设太短会导致偶尔网络抖动时点播中断,设太长会占用无用端口。30 秒是折中值。除此之外还有设备密码的配置项,这个是国标注册过程的密码校验,一般设备端默认填平台的 ID 或者统一密码,要和这里的password保持一致。
3.3 启动平台与检查设备注册状态的三个命令
配置改完之后启动平台,用 Spring Boot 的标准方式即可:
# 前端启动方式,适合本机调试,日志直接打在终端 java -jar jgb28181-platform.jar --spring.profiles.active=dev # 生产环境中推荐后端启动方式,日志重定向到文件 nohup java -jar jgb28181-platform.jar --spring.profiles.active=prod > logs/platform.log 2>&1 & # 启动后确认 SIP 端口处于监听状态 netstat -unlp | grep 5060启动后第一个要确认的是 UDP 5060 端口是否真的在监听。用netstat检查时注意加-u参数,只看 TCP 看不到 UDP 监听。接下来在设备管理平台里添加一台设备,填入平台 IP 和 SIP 域,然后观察日志。如果设备注册成功,日志中会出现一条类似“设备注册成功:34020000001320000001”的记录,同时平台会自动发起目录查询,拉动设备下的通道列表。如果没看到注册日志,先用 tcpdump 在服务器上抓 UDP 5060 端口确认报文是否到达:
tcpdump -i any udp port 5060 -n -vv这段抓包能快速区分问题方向。有包到了服务器但平台没反应,是平台解析问题;包根本没到,是网络或设备配置问题。实际调试中用 tcpdump 二十秒就能定位八成以上信令问题,比来回改设备配置高效得多。
3.4 走通第一路实时点播:从 API 到 RTP 收流的全链路验证
设备在线后,第一路实时点播的验证流程值得完整走一遍。平台暴露的接口一般长这样:
# 查询设备下的通道列表,从返回结果里拿到 channelId curl http://192.168.1.10:8080/api/devices/34020000001320000001/channels # 发起实时点播请求 curl -X POST http://192.168.1.10:8080/api/play/start \ -H "Content-Type: application/json" \ -d '{ "deviceId": "34020000001320000001", "channelId": "34020000001320000001", "streamType": "live", "ssrc": 123456, "mediaHost": "192.168.1.10", "mediaPort": 30000 }'发起点播后,平台的内部处理链路大致是:收到 HTTP 请求后创建一个会话,从端口范围里分配一个 RTP 收流端口,把媒体地址放在 INVITE 请求的 SDP 描述里下发给设备,设备解析 SDP 后用 RTP 协议把码流推到对应端口。所以验证时要盯两个层面——信令层看 INVITE 是否发送、设备是否回了 200 OK;媒体层看 RTP 端口是否收到 UDP 包。用下面的命令看收流情况:
# 观察指定 RTP 端口收到多少包 tcpdump -i any udp port 30000 -n | head -50 # 如果 RTP 端口收到连续包,说明信令链路和媒体发流链路都通了 # 此时在平台里停止点播,确认平台向设备发 BYE 请求释放会话这个流程如果一路走通,整套平台的核心链路就没有问题了。第一路通了之后再挂多路设备,基本就只是端口分配和数据库写入的性能问题。值得提醒的是,点播成功后要在终端拉一段流到 VLC 里验证可播放性,有的平台能做到“收到 RTP 包但 PS 封装解析有问题”,表现是平台显示流在线但画面出不来,这个需要结合信令和 SDP 协商来排查。
4. JGB28181 平台的避坑指南:注册不上、点播黑屏、掉线的真相都在这里
4.1 设备注册一直失败,平台日志里连 SIP 报文都看不到
现象:设备配置了平台 IP 和端口,状态一直显示离线,平台日志没有任何收到注册消息的记录。
原因:UDP 端口被防火墙拦截是最常见的原因,其次是设备配置的 SIP 服务器地址不可达。云服务器上安全组规则只放行了 TCP 端口,而国标 SIP 注册走的是 UDP 5060,报文直接被丢弃。
解决:先在服务器上执行抓包确认报文是否到达,再用firewall-cmd --add-port=5060/udp --permanent添加 UDP 端口放行;云服务器则需要在安全组同时添加入方向的 UDP 5060 和 RTP 端口范围规则。我一般会把 RTP 端口范围和业务端口一并放行,避免后续联调再次踩坑。
4.2 注册成功了,但点播画面一直黑屏,平台能看到建流,客户端播放无输出
现象:平台日志显示注册成功、点播正常、RTP 收到大量包,但用 VLC 拉流没有画面。
原因:两个方向最常见。第一,平台发给设备 INVITE 请求里的 SDP 携带的媒体地址填了监听地址或内网地址,设备的 RTP 流发到了错误的地址;第二,收到的 RTP 流是 PS 封装的 H.264,但播放器不支持,或者流里没有关键帧,播放器拿到的是不完整的码流无法起播。
解决:第一类问题在平台配置里把 SIP 服务 IP 和媒体收流 IP 明确填为服务器实际网卡地址;第二类问题需要先抓包看 RTP 包内容,确认是 PS 封装,然后检查是否有关键帧。如果视频编码是 H.265,要确认播放端是否支持。平台如果能配置转码,可以先把 H.265 转成 H.264 验证链路,再排查播放端解码能力。另外还有一个点是 SDP 里的y=字段,有些设备要求这个字段的 SSRC 和实际发送的 SSRC 一致,不一致设备也会推流,但平台侧会话匹配会出问题。
4.3 设备稳定运行一段时间后频繁掉线,心跳超时和网络扰动分不清
现象:设备上线后运行几个小时,随后在平台里频繁上下线,每次间隔几十分钟。
原因:国标设备依靠 SIP 心跳保活,默认心跳周期一般为 60 秒,平台需要在设备连续三次心跳超时后才判定离线。如果平台里心跳超时时间配置过短,比如设成了 30 秒,网络只要有一次延迟就会误判离线。另一个原因是平台侧的设备表更新有并发问题,多条设备的注册和心跳消息同时到达时丢失了部分更新。
解决:把心跳超时时间设成心跳周期的 3 倍以上,比如设备 60 秒心跳,平台配置 180 秒超时。把设备状态更新操作加上同步锁或改为原子更新,避免并发写入覆盖。还可以在设备管理页面加一个“最后心跳时间”字段,结合 NTP 校时观察,能更准确判断是平台判离线还是设备真离线。
4.4 级联对接上级平台时,上级平台看不到下级设备
现象:平台同时开启了作为“下级平台”向上级级联的配置,级联显示成功,但上级平台列表中看不到注册上来的通道。
原因:国标级联的流程是两个层面:平台向“上级平台”注册,注册成功后平台要主动发送目录查询请求,推送本域内所有设备的信息。很多实现只做了注册,忽略了注册成功后主动推送目录的步骤。
解决:在平台的 SIP 模块里检查是否有注册成功后自动发送目录信息的实现。如果没有,需要手动在管理页面触发“目录推送”,或者改造代码,在收到上级平台的 MESSAGE 目录查询消息后,把本域所有设备信息封装成 XML 回复。这个逻辑并不复杂,但漏掉一步就会导致上级平台侧一片空白,定位时需要同时在上级平台看信令日志,区分是“注册失败”还是“目录查询没有响应”。
4.5 平台重启后设备全部离线,注册鉴权密码错乱
现象:平台重启几分钟内,所有设备疯狂重连,但大部分设备注册失败。
原因:国标注册有密码校验机制。设备端配置的注册密码是固定的,平台配置正确的情况下重启只需要重新注册一次就能成功。但如果平台的数据表被重置过,设备 ID 和密码的映射表丢失,或者设备端配置的平台 SIP 域不对,就会反复重试失败。
解决:重启前先备份数据库的device表,确认设备的密码字段没有被重新初始化为默认值。批量接入时建议用统一密码并固定在两端配置中,减少单点配置错误。遇到重启后大批量重连失败,先抓包确认设备是否收到 401 未授权的响应,收到就是密码校验问题,直接看两端的密码配置。
5. 把 JGB28181 平台用得更顺手的三个技巧
5.1 用级联模式把平台当作上级平台接入未知名设备
很多场景下 JGB28181 平台不只是接入设备,还要作为下级平台对接第三方的国标平台。级联调试三步法可以帮你快速跑通:先在参考配置里开放“平台接入域”,步骤为:
# 添加一个上级平台账户,配置上级平台的 SIP 域和 IP curl -X POST http://localhost:8080/api/platforms/add \ -H "Content-Type: application/json" \ -d '{ "platformDomain": "34020000002000000002", "platformIp": "192.168.2.20", "platformPort": 5060, "deviceId": "34020000002000000001" }'平台会向目标 IP 发起注册请求,并默认推送全部设备信息。如果上级平台迟迟看不到通道,用 tcpdump 抓 5060 端口看两边是否完成 REGISTER 事务,再重点看上级平台是否发送CATALOG查询,以及回复的 XML 里设备编码是否正确。级联模式下最重要的是“域”和“设备 ID”必须全国标规范,否则上级平台校验不过,注册就失败。
5.2 用抓包工具区分信令问题和媒体问题
排查国标问题时,80% 的“疑难杂症”可以通过抓包一次性定性。信令层面,重点看 REGISTER 消息是否被回复,INVITE 消息的 SDP 内容是否包含正确的 IP 和端口;媒体层面,看 RTP 包的 payload type 和 SSRC 是否跟 INVITE 中协商一致。SSRC 不对是常见的不显示画面的原因,因为平台侧不知道收到的包属于哪个会话。抓包时建议同时抓 SIP 端口和分配的 RTP 端口,把信令和媒体放在一个 pcap 文件里,回放时能对应起来。
5.3 平台稳定运行的日常维护:日志轮转与端口回收检查
平台长时间运行后有两个容易被忽视的点。一是日志文件没有配置轮转策略时,debug 级别日志会轻松撑爆磁盘,Spring Boot 中可配置logback-spring.xml按天轮转并保留 7 天。二是端口回收异常,点播断开后,如果代码里有Socket.close()被异常跳过或超时线程未清理,端口会逐步耗尽。日常巡检可以写一行脚本检查已使用端口数量:
# 统计当前媒体端口的占用数,如果持续增长不下降,说明有会话泄漏 netstat -unlp | grep -E '30000|30001|30002' | wc -l正常情况端口数是和活跃点播数对应的;如果断开后端口数不降,就要去会话表里看是否有残留记录。自从有一次因为端口泄漏导致扩容前一天晚上 4 路流直接推不出去之后,我养成了每次上线前都跑一遍端口检查脚本的习惯。这套平台只要把注册、点播、级联这三条链路摸透,后面维护起来基本没有大坑。希望帮到你。
本文还有配套的精品资源,点击获取