简介:一份基于Java实现的GB28181协议平台源码,面向视频监控与安防领域的开发者。该平台遵循国标信令规范,覆盖设备注册、状态恢复、目录查询、实时视频流传输等核心场景,可用于解决国标设备接入与服务端通信的常见问题。压缩包内共49个文件,其中43个为Java源文件,承担主要业务逻辑;2个XML文件与多个配置文件负责参数设定,另有文档和版本管理辅助文件,整体仅64KB,结构紧凑,适合按源码顺序学习。目前平台已实现注册、恢复、目录查询以及实时视频流(支持TCP被动与UDP两种模式)等功能,修改配置后即可编译运行,方便快速搭建测试环境。当前已有4240人学习下载,对于希望理解国标协议实现、信令流程和流媒体传输机制的Java开发者而言,这是一份可直接参考的开源工程,兼具学习与二次开发价值。 做视频监控平台接入的人,应该都体会过“厂商SDK各写各的”的痛:海康一个SDK、大华一个SDK、其他小厂再来一个,接口风格千奇百怪,光是适配就能磨掉半个团队。GB28181就是冲着这个痛点来的——它定了一套标准的联网协议,注册、预览、录像、语音对讲全给你规范好,设备只要支持国标,平台就能用统一的方式去接入。而JGB28181这个项目,就是用Java把这套国标平台从零搭起来的一个完整示例,信令、媒体、设备管理都在里边。
这篇文章我从协议理解讲到代码落地的关键环节,再把我实际调试设备时踩过的坑一并列出来。不管你是准备在公司内部自研一套视频接入平台,还是想快速搞定上级平台的级联对接,这篇都值得当一份实操手册来参考。
1. 整体架构与设计思路:为什么视频平台要拆成“信令面”和“媒体面”
1.1 GB28181到底在管什么
GB28181全称叫做《公共安全视频监控联网系统信息传输、交换、控制技术要求》,名字听着很长,本质一句话:它规定了视频监控设备之间、平台之间怎么“打招呼”和“传视频”。
具体拆开看,国标定义了三大块内容:第一块是信令控制,设备注册、心跳、目录查询、云台控制、录像回放这些都是走信令的;第二块是媒体传输,摄像头拍出来的H.264/H.265视频流怎么封装、怎么通过网络传到平台;第三块是系统互联,也就是上下级平台之间如何级联、如何把资源目录共享出去。在JGB28181里,这三个部分被分别处理:SIP层负责信令,RTP/PS流处理负责媒体,设备目录与状态管理负责互联。这样拆分的好处在后边排查问题时非常明显——信令出问题了抓SIP包,画面出问题了抓RTP包,互不干扰。
1.2 Java生态怎么接SIP和RTP
很多人听到GB28181第一反应是“这玩意儿不是C/C++的天下吗”。早期确实如此,好多厂家提供的SDK都是C++动态库。但Java生态做国标平台不是不行,反而有几个天然优势。
第一,SIP协议本身就是文本协议,解析报文用Java的字符串处理能力绰绰有余,而且成熟的开源SIP栈比如MJSIP(基于JAIN SIP)、RestComm等可以直接复用。第二,媒体流接收依赖高性能网络IO,Netty对UDP高吞吐场景支持非常好,处理一路摄像头的RTP流完全不是问题。第三,视频联网平台不是只有协议解析,还有设备管理、权限控制、Web展示、录像检索,这些业务逻辑用Java做起来效率远高于C++。实际项目中,我通常把SIP信令服务和RTP媒体服务放在同一个进程里,方便内部通信,同时用消息队列或本地回调把业务层和协议层解耦,避免协议细节污染上层业务。
1.3 为什么不直接选RTSP/ONVIF
聊GB28181绕不开一个对比问题:为什么不用RTSP或者ONVIF?RTSP适合单点拉流,比如一个播放器去拉一个摄像头,但面对几千路摄像头的平台接入,RTSP在设备发现、目录管理、级联共享这些场景几乎没有规范支持。ONVIF偏向设备管理控制,媒体传输部分却相对弱。GB28181的强项是平台级联网:设备只需要知道平台地址,注册上来之后,平台可以查询目录、按需拉流、甚至把资源共享给上级平台,这些都是为规模化运营设计的。所以,如果你的目标是“统一接入、统一管理、向上级共享”,GB28181几乎是唯一选择。
2. 核心流程:从设备注册到实时预览的完整链路
2.1 REGISTER注册鉴权与周期心跳
设备接入平台的第一步是注册,走的是SIP的REGISTER方法。你可以把SIP理解成“视频界的电话系统”,设备就是打电话的人,平台就是交换机。设备向平台发送一个REGISTER请求,平台收到后返回401要求鉴权,设备用密码计算摘要信息再次发送REGISTER,平台校验通过后返回200 OK。这个过程和普通电话的认证逻辑类似,只不过认证的相关参数在SIP头域的Authorization字段里。
以下是一个典型REGISTER报文的简化结构:
REGISTER sip:34020000002000000001@192.168.1.10:5060 SIP/2.0 Via: SIP/2.0/UDP 192.168.1.20:5060;rport;branch=z9hG4bK12345 From: <sip:34020000001320000001@192.168.1.20>;tag=abc To: <sip:34020000002000000001@192.168.1.10> Call-ID: shell@192.168.1.20 CSeq: 1 REGISTER Expires: 3600 Content-Length: 0注册成功不等于万事大吉。国标要求设备周期发送心跳,默认60秒一次,通过MESSAGE方法携带Keepalive类型的XML消息。平台如果在指定时间(一般3个心跳周期)内收不到心跳,就会把设备标记为离线。我要特别提醒:心跳超时时间的配置别太短,有些设备网络抖动一下,你直接把它判定下线,会引起大量误报,我一般把离线判定时间调到心跳周期乘以4。
2.2 INVITE拉流与SDP媒体协商
注册搞定之后,用户点开预览,平台向设备发起INVITE请求,这就是SIP里的“拨号”过程。INVITE请求的消息体里携带SDP信息,描述平台希望接收什么样的媒体流。
一个典型的视频SDP片段看起来这样:
v=0 o=34020000002000000001 0 0 IN IP4 192.168.1.10 s=Play c=IN IP4 192.168.1.10 t=0 0 m=video 10000 RTP/AVP 96 98 a=recvonly a=rtpmap:96 PS/90000 a=rtpmap:98 H264/90000设备收到INVITE后,如果认可这个媒体参数,就回复200 OK,并在SDP里写上自己的媒体地址和发送方向。之后平台回一个ACK,设备就开始往平台指定的IP和端口推RTP流了。整个过程对应到电话就是:拨号(INVITE),对方接听(200 OK),确认接通(ACK),开始说话(RTP流)。
这里有个坑:部分厂商的设备在发完200 OK后并不会等ACK,而是立刻开始推流,平台必须在发送200 OK响应之前就把收流UDP端口准备好,否则前几个RTP包就丢了,画面可能起不来或者出现短暂花屏。
2.3 设备管理信令:目录查询、云台控制与语音对讲
除了注册和拉流,国标还定义了很多常用操作。目录查询:平台向设备发送MESSAGE,携带Catalog查询指令,设备返回以XML形式组织的设备列表,里面包含摄像头通道ID、名称、经纬度、状态等信息。云台控制:平台下发Control指令,控制摄像机上下左右转动、变倍变焦、预置位设置等。语音对讲:平台向设备发起音频方向的INVITE,协商一个双向RTP音频通道,把平台麦克风采集的音频发给设备,同时把设备端音频收回平台。
这些操作看起来是单个指令,但需要注意请求超时和事务匹配。SIP是事务型协议,一个请求必须有对应的响应,平台实现时要建立Call-ID与操作类型的映射关系,防止多个指令交叉错乱。我自己在实现时就是用一个ConcurrentHashMap缓存“Call-ID -> 实际业务操作”,收到响应后按Call-ID取回上下文,再驱动业务状态机推进。
3. 实操落地:把一个GB28181平台跑起来
3.1 环境准备与工程骨架
JGB28181这类项目选型时,我的建议是直接以Spring Boot为底座,原因很简单:设备管理、用户权限、REST API这些都要用,Spring Boot能省掉大量重复劳动。基础环境需要JDK 8或17、Maven 3.6+、MySQL、Redis。JDK17的虚拟线程在高并发信令场景有优势,但如果你的团队对旧版本更熟,JDK8也完全够用,别为了追新而折腾。
工程内部我一般拆成三个模块:sip-server负责SIP协议栈的启动和请求分发,stream-handler负责RTP流的接收、PS解封装和转码推流,web-manager负责设备管理、实时预览页面、录像检索等业务。模块之间通过Spring事件机制或者内部HTTP接口通信,尽量避免直接依赖对方的具体实现类。
核心依赖大概这样:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency> <dependency> <groupId>javax.sip</groupId> <artifactId>jain-sip-api</artifactId> <version>1.2.1.4</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency>3.2 SIP服务端代码的关键组成
实现SIP服务端并不需要把整个JAIN SIP源码看一遍,重点只需要实现监听器接口,针对几个关键方法做业务分发。我会在SipListener的processRequest里做一层拦截:
public class Gb28181SipListener implements SipListener { @Override public void processRequest(RequestEvent requestEvent) { String method = requestEvent.getRequest().getMethod(); switch (method) { case Request.REGISTER -> deviceRegisterService.handle(requestEvent); case Request.MESSAGE -> messageService.handle(requestEvent); case Request.INVITE -> inviteService.handle(requestEvent); case Request.BYE -> byeService.handle(requestEvent); case Request.ACK -> ackService.handle(requestEvent); default -> log.warn("unsupported method: {}", method); } } @Override public void processResponse(ResponseEvent responseEvent) { // 处理平台作为客户端发起的请求的响应,例如向上一级平台拉流时 responseEvent.getResponse().getStatusCode(); } }这里有一个非常容易被忽略的细节:JAIN SIP的监听器默认是单线程回调,如果业务处理太慢会阻塞整个协议栈。我的做法是,在processRequest里先把RequestEvent交给一个独立线程池处理,监听器本身只负责快速返回。线程池大小按预期设备数量配置,我一般设为核心线程8、最大线程32、队列容量2000,避免设备批量上线时把CPU打满。
3.3 媒体流接收、转封装与分发
设备推上来的RTP流,裸内容其实是PS封装(Program Stream)的H.264/H.265数据,播放器不能直接识别。所以媒体面要做的事情是:用Netty监听UDP端口接收RTP包,把PS包从RTP payload里剥离出来,解PS封装拿到H.264裸流,然后转封装成FLV、HLS或RTMP输出给Web端播放。
public class RtpServerInitializer extends ChannelInitializer<DatagramChannel> { @Override protected void initChannel(DatagramChannel ch) { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new RtpDecoder()); // RTP头解析 pipeline.addLast(new PsDepacketizer()); // PS包解封装 pipeline.addLast(new H264ToFlvHandler()); // H264转FLV tag } }实际部署时,很多团队会把转封装这层剥离出来交给ZLMediaKit这类C++流媒体服务独立处理,Java只负责SIP信令和回调通知。Java进程收到设备RTP流后转发给ZLMediaKit的RTMP端口,ZLMediaKit统一输出HLS/FLV/WebRTC,性能和稳定性都会好很多。不过纯Java方案也不是不行,单机支持一百路以内并发预览完全不吃力,主要瓶颈在转封装CPU消耗,建议把转码线程池独立出来,别跟业务线程互相抢占。
3.4 国标设备ID编码规则
做GB28181平台无论如何绕不开20位国标编码。很多人第一次接触就被这一串数字搞晕,我把它拆成表格就非常清晰:
| 位段 | 长度 | 含义 | 示例 |
|---|---|---|---|
| 中心编码 | 8位 | 行政区划/行业中心编码 | 34020000 |
| 行业编码 | 2位 | 接入的行业域编码 | 20/21/22等 |
| 类型编码 | 1位 | 设备类型:1是中心、2是行业、4是摄像机等 | 4 |
| 序号 | 7位 | 设备域内序号 | 0000001 |
| 校验位 | 2位 | 前18位算出的CRC校验码 | 01 |
平台自身的ID是20位编码,摄像头的编码也遵循同一套规则,只是类型编码不同。校验算法核心是CRC-16,如果你用Java写,可以直接用现成的CRC16工具类,但一定要注意多项式参数和国标要求的保持一致,否则设备会直接拒绝注册。我踩过这个坑,平台编码校验位算错,排查了一个多小时才发现是CRC参数问题。
4. 常见问题与排查技巧实录
4.1 设备注册不上,先查这五个点
设备注册不上是最常见的问题,也是最能拉开排查效率差距的场景。我自己的排查顺序是:第一,抓包看SIP端口有没有收到REGISTER,如果设备根本发不出来,检查设备配置里的平台IP和端口是否正确;第二,收到REGISTER后平台有没有返回401,如果返回的是400/500,多半是SIP消息格式不符合国标规范;第三,设备带鉴权信息的第二次REGISTER有没有到平台,如果没到,检查设备和平台之间的网络是否有SIP ALG干扰;第四,平台返回200 OK后设备有没有确认,如果设备日志显示注册成功但平台列表里还是离线,大概率是设备与平台对心跳字段的解析不一致;第五,检查设备ID和平台ID是否在同一个中心编码下,很多老设备对跨域注册会直接拒绝。
我强烈建议在任何排查开始之前,先学会用Wireshark的sip过滤条件。一条一条看信令交互,比看任何平台日志都直观。
4.2 拉流黑屏或花屏的根因
画面能出但黑屏,问题基本出在媒体协商或RTP传输层面。先确认SDP里平台填的是recvonly还是sendonly,方向填错设备会直接把流发出去但平台没接收。然后看RTP包是否到达了平台收流端口,常用的排查命令是在平台服务器上执行netstat -unlp看UDP端口是否有数据增长。如果端口有数据但画面依然黑屏,基本可以锁定在PS解封装层,最常见的坑是RTP payload里PS包跨包分段,解封装时没有做正确的拼接。
花屏则多与丢包和数据错序有关。局域网内高带宽之下不该有大量丢包,如果RTP序列号不连续,检查中间网络设备有没有对UDP做限速策略。还有一个隐藏问题:多路摄像头并发拉流时,平台监听端口如果只有一个UDP Socket,高并发容易丢包。我的解法是采用端口池,给每路会话分配独立的RTP收流端口,从根上隔离相互影响。
4.3 语音对讲没有声音
语音对讲这类双向音频场景,问题比视频多一个方向维度。第一,确认设备SDP里m行确实是audio类型,且rtpmap声明的编码是PCMA或者PCMU,很多设备默认没有开启音频编码协商,需要手动在设备端配置。第二,确认平台发出去的音频方向和设备期望一致,有些设备是sendonly,有些是sendrecv,如果你只按recvonly去对接,平台的音频根本推不进去。第三,G.711编码数据裸流播放时需要注意采样率和声道匹配,我遇到过一次反反复复没声音,最后发现是设备回传的音频采样率是16k,而平台按8k解码,音调和节奏全乱掉了。
语音对讲还有一个经常被忽略的问题:回声。平台端如果没有做回声消除,说话的人会听到自己的声音延迟返回,体验非常差。实现级别可以在采集端过滤参考信号,或者直接用带AEC的音频处理库做一轮处理再编码发送。
4.4 并发、级联和长时间运行的稳定性
项目上线前一定要做一轮长时间稳定性验证。SIP的UDP消息没有连接保活,长时间运行后可能出现内存里堆积大量超时事务对象,导致内存缓慢增长。我的处理方式是启动一个定时任务,把超过60秒没有匹配到响应的事务全部清理掉,相当于给信令层做了一次垃圾回收。
级联场景会引入新的复杂度:下级平台向本级注册,本级再向上级注册。在这个链条上,每个平台的设备编码必须唯一,否则上级平台会因为编码冲突把设备抛弃。而且信令超时时间在级联下要逐级放大,下级平台到本级是1秒超时,本级到上级可能就需要2到3秒,否则中间网络稍有抖动,拉流任务就会反复重试,把带宽打满。
另外,UDP动态端口在长时间运行后可能被占用完,平台一定要配置收流端口池并加入释放机制。收到BYE或者设备离线后,立即释放对应端口和内存资源,否则运行一周后平台大概率出现“新设备拉不动流”的诡异现象。
提示:如果平台要对外开放公网接入,务必把SIP信令端口和RTP媒体端口分开映射,千万别只映射5060端口。大量“设备注册成功但看不到视频”的问题,都是NAT下媒体端口没有映射导致的。
我在实际做国标平台的过程中,最大的感受是:协议本身并不难,难的是各种设备之间“看似标准、实则各搞一套”的兼容性适配。JGB28181这类Java实现的价值,恰恰在于把复杂的协议栈封装得相对可控,遇到问题可以快速看源码、改逻辑,而不是对着厂商文档干瞪眼。最后分享一个调试技巧:手头没有真机的时候,用SIPp模拟设备注册和INVITE拉流,能让信令联调效率提升好几倍。等真机到位之后,再把个别兼容性差异逐个打磨,整个平台就会越用越稳。
本文还有配套的精品资源,点击获取